Compare commits

...

120 commits

Author SHA1 Message Date
Jeff Epler
c104c92a86 Doxygen needs to know where to look for the image 2022-04-18 11:41:53 -05:00
Jeff Epler
145efdb695 clang-format 2022-04-18 11:33:07 -05:00
Jeff Epler
2421b2be81 Add some text on the front page of the docs 2022-04-18 11:07:54 -05:00
Jeff Epler
602a489d46 remove straggler file 2022-04-18 09:13:45 -05:00
1841d0f0c2
Merge pull request #16 from jepler/applefloppyflux
Add Apple Disk ][ Flux Writing for rp2040
2022-04-16 12:54:52 -05:00
2629a9742b
fix doxygen 2022-04-12 08:59:31 -05:00
b43c9764db
document constructor parameter 2022-03-30 12:30:07 -05:00
7851e7574a
fix return code from write_track 2022-03-30 12:29:28 -05:00
27a4abf6b4
Fix flux writing, implement GETFLUXSTATUS better
I initially thought (and didn't test) that an error from
fluxwrite could be signaled in place of the initial ACK
but in the protocol this ACK has to be written before the
flux is sent from the PC. So, save the result and use it down
in GETFLUXSTATUS instead.
2022-03-30 12:28:25 -05:00
c6e5044488
Add Apple Disk ][ Flux Writing for rp2040
The Apple Disk ][ produces a flux transition whenever the
WRDATA signal has a transition, rather than on only one edge.
This needs different low-level writing code than a PC drive.

We can implement it later for samd51 or bitbang if needed.
2022-03-30 11:36:47 -05:00
7f66508cd8
Merge pull request #15 from jepler/applefloppy
Add support for Apple II Floppy drives such as Disk ][
2022-03-15 12:43:22 -05:00
2b67aa391e
samd build fixes 2022-03-15 11:35:11 -05:00
680197ded5
Fix write-protect sensing
To ensure it's OK to activate winding 0, it's necessary
to go to a quartertrack that is a multiple of 8, not just 4.

The previous version of code could lead to loss of position when
the write-protect sense was read while the full track was
an odd number: 1, 3, ...
2022-03-15 08:59:20 -05:00
6bf313acff
rp2040: Disable interrupts a skosh earlier
.. this helps get the state machine fifo good and packed,
avoiding a glitch during the first byte of the track.

Also superstitiously disable the PIO instance, even though it
should have been disabled already.
2022-03-15 08:59:20 -05:00
48e1309e69
apple2: fix pinout 2022-03-15 08:59:19 -05:00
c6227a80e8
Fix flux reading when index pulse doesn't come
It's possible for `capture_track` to return early, in the
case that the buffer is small relative to the track time
(or something else is going wrong, like no index pulse).

However, in this case the flux sending loop would never exit,
but would index beyond the end of the flux_transitions array
and probably HardFault eventually.

Instead, use (-1) as the sentinel value for 'no index pulse arrived'
and break out of the loop.
2022-03-15 08:59:19 -05:00
8d38c01185
apple2floppy: Show write protect status in flux capture demo 2022-03-14 16:54:15 -05:00
77de2e119b
Apple2Floppy: fix polarity of write protect signal 2022-03-14 16:53:41 -05:00
c50785c897
clang a bit more; provide pin definitions (unteseted) for feather m4 2022-03-10 16:22:18 -06:00
c21929b34b
doxygen improvements 2022-03-10 16:07:40 -06:00
e38f380fc6
std::end is not available for samd51 2022-03-10 16:02:54 -06:00
1508b08997
Add support for apple2 floppy drives to the greaseweazle sketch
.. this involves making some refinements to the floppy base class
as well.
2022-03-10 13:33:37 -06:00
98c4b2d098
doxify 2022-03-09 12:02:10 -06:00
9c7e3ff22c
Get apple2 reading working, add example 2022-03-09 11:47:59 -06:00
bd1669e8b0
apple floppin code is syntactically valid, not tested on hw yet 2022-03-08 17:36:27 -06:00
00cfcf1963
rp2: fix return valaue of capture_foreground() 2022-03-08 16:51:39 -06:00
d6dc0fe043
Factor out base class
... in preparation for one that does apple floppin'
2022-03-07 17:57:45 -06:00
5d1e32d7f1
Merge pull request #12 from jepler/revamp-ci
Create artifacts for greaseweazel and msd examples
2022-02-11 16:22:25 -06:00
b650a78743
fix checkout path of the ci scripts 2022-02-11 12:59:02 -06:00
1587684f3e
examples: reformat all with ctrl-t in arduino ide 2022-02-11 09:42:38 -06:00
a71f107556
greaseweazel: improve index pulse recording
ripping with genuine GW hardware, a typical track is read in
to gw as
```
Flux: 72.00 MHz
 Total: 232202 samples, 594.17ms
 Revolution 0: 93.06ms
 Revolution 1: 166.87ms
 Revolution 2: 166.87ms
 Revolution 3: 166.87ms
```

our firmware came in as
```
Flux: 24.00 MHz
 Total: 120485 samples, 652.35ms
 Revolution 0: 0.00ms
 Revolution 1: 162.34ms
 Revolution 2: 55.11ms
 Revolution 3: 162.17ms
 Revolution 4: 55.28ms
 Revolution 5: 162.12ms
```
Notice how there's an empty revolution 0 and a partial revolution
2 & 4.

It appears that
 * if capture_ms is 0, nothing is captured after the index
   (rather than 50ms)
 * no index is emitted at the start (though this is because
   the first revolution is almost always a partial revolution)

Now after this change, a track on rp2040 looks like
```
Flux: 24.00 MHz
 Total: 96498 samples, 502.46ms
 Revolution 0: 0.00ms
 Revolution 1: 167.49ms
 Revolution 2: 167.49ms
 Revolution 3: 167.49ms
```
which appears to be closer to what gw expects; it also fixes how
g64conv used to require `r1` to select a particular revolution.
This may have been because it defaulted to one of the 'runt'
revolutions, which also made it misestimate the rotational speed
of the drive.
2022-02-10 20:05:04 -06:00
87785ae5ff
rp2040: move index handling out of pio, correct falling_index_offset 2022-02-10 19:59:35 -06:00
ab501a60d2
Merge pull request #11 from adafruit/samd51_capturetimer
Implement timer based capture on samd51, pio based capture on rp2040
2022-02-10 13:20:57 -06:00
cd0c08142e
Let's upload some artifacts 2022-02-10 13:18:35 -06:00
d18495d298
Remmove F_CPU warnings, they are irrelevant now 2022-02-10 09:53:46 -06:00
311c7798fb
GW firmware is unreliable unless Adafruit TinyUSB is used 2022-02-10 09:50:19 -06:00
a400e0b8e2
remove standalone mfm test program, is no longer needed 2022-02-09 17:49:25 -06:00
178792a525
Fix greaseunpack for 2-byte codes
it was off by 250.
2022-02-09 17:48:38 -06:00
c78b45ba9d
Remove some debug printing
.. this caused a problem when the flux bins were >512, I think.
2022-02-09 17:48:18 -06:00
b7754468c5
remove unused wait_index, stop_index 2022-02-09 16:47:19 -06:00
67bc164530
fix header 2022-02-09 16:47:10 -06:00
43e02a5545
no more mhz warning for rp2040 2022-02-09 16:46:59 -06:00
e4a29020be
Fix capture
.. after awhile it would run out of program space, because
it was not freeing the right program.

also add support for terminating a read after a given number
of ms, or 50ms after the second index pulse.
2022-02-09 13:45:06 -06:00
lady ada
0908c92ab5 fix 0 revs is 0 revs 2022-02-08 18:16:15 -05:00
46166ace07
mark functions as private to quiet doxygen 2022-02-08 16:47:53 -06:00
ef7eca9d02
Merge remote-tracking branch 'origin/samd51_capturetimer' into samd51_capturetimer 2022-02-08 16:42:50 -06:00
afe73d8039
rp2040: implement flux writing
This nearly works with fluxengine (slightly older revision):
```
$ ./fluxengine write ibm1440  -i bloop.img  --usb.serial=...
Measuring rotational speed... 200ms
Writing to: drive 0
  0.0: Write:   199 ms in 76988 bytes
       Verify:  391 ms in 150010 bytes
  0.1: Write:   199 ms in 92225 bytes
       Verify:  326 ms in 150006 bytes
  1.0: Write:   199 ms in 79272 bytes
       Verify:  380 ms in 150002 bytes
```
but at some point, the fw and fluxengine stop successfully
talking:
```
Measuring rotational speed... 200ms
Writing to: drive 0
  0.0: Write:   199 ms in 76988 bytes
       Verify:  391 ms in 150010 bytes
  0.1: Write:   199 ms in 92225 bytes
       Verify:  326 ms in 150006 bytes
  1.0: Write:   199 ms in 79272 bytes
       Verify:  380 ms in 150002 bytes
```
2022-02-08 16:42:46 -06:00
lady ada
7db1daa364 some doxyclang 2022-02-08 12:01:14 -05:00
lady ada
b41052ba75 Merge branch 'samd51_capturetimer' of github.com:adafruit/Adafruit_Floppy into samd51_capturetimer 2022-02-08 11:41:11 -05:00
lady ada
4d062e5244 allow writing long fluxes, fix for 'capture by time not revs' 2022-02-08 11:41:05 -05:00
fefdb6667e
run clang-format 2022-02-07 21:25:58 -06:00
6e614e6043
fix capture and do proper greaseweazel flux packing on samd51 reading 2022-02-07 21:09:49 -06:00
10eb2fd3cc
run clang-format 2022-02-07 20:44:20 -06:00
85e0106965
Fix write_track for rp2040 (but timings are likely wrong) 2022-02-07 20:43:55 -06:00
5a2fea5616
Make samd51 work again with sampled flux 2022-02-07 20:40:21 -06:00
4aebea3518
convert mfm to use pio on rp2040 2022-02-07 20:12:14 -06:00
lady ada
d374cf6500 we handle packed bytes 2022-02-07 20:16:51 -05:00
lady ada
9037df8f75 write 2022-02-06 18:39:59 -05:00
lady ada
a381e34486 can read and write now 2022-02-06 18:36:55 -05:00
lady ada
72dc40889e start adding writing - but also make sure that if read and write use the same timer we dont stomp all over the place by deiniting and cleaning up after a track capture 2022-02-05 23:37:31 -05:00
lady ada
c747af3a87 avoid infinite loop 2022-02-05 22:53:57 -05:00
lady ada
5a467596b8 Merge branch 'samd51_capturetimer' of github.com:adafruit/Adafruit_Floppy into samd51_capturetimer 2022-02-05 22:24:33 -05:00
lady ada
39fdddc976 add gw timing for print debug 2022-02-05 22:24:27 -05:00
a07ac41c11
fix samd51 build 2022-02-04 13:23:14 -06:00
f25c9afc82
run clang-format 2022-02-04 13:18:27 -06:00
24e8ea588f
ignore ci-arduino if it exists 2022-02-04 13:18:10 -06:00
9aeee24ab8
No need to overclock for flux reading anymore :) 2022-02-04 13:16:40 -06:00
235c2f1923
Implement rp2040 pio reading & reorganize 2022-02-04 13:16:34 -06:00
lady ada
b0b9ad9286 doxyfile 2022-01-25 17:06:09 -05:00
lady ada
b16fb7f0b7 doxyclang 2022-01-25 17:04:19 -05:00
lady ada
9c744073ad clang 2022-01-25 16:56:44 -05:00
lady ada
91c3428671 store in gw format if desired 2022-01-25 16:51:13 -05:00
lady ada
61b5fad6fe dont trim pulses, we pre-encode in gw format 2022-01-25 16:45:48 -05:00
lady ada
35f3a884f5 deal with long pulses 2022-01-25 16:35:03 -05:00
lady ada
bd9e17f012 readd timeout but make it 10 seconds. also check that we could init the pins 2022-01-25 13:31:10 -05:00
lady ada
bd06c2f941 moreclang 2022-01-25 11:53:52 -05:00
lady ada
9a790cc7c9 24 mhz sample rate should be enough for anyone 2022-01-25 11:44:33 -05:00
lady ada
5fcedf8935 fix xample 2022-01-25 11:44:16 -05:00
lady ada
1e07c6be94 readd the writeflux stuff, packing the indexop in the flux stream 2022-01-25 11:30:32 -05:00
lady ada
4af555fd29 let us query the sample frequency. doxyclang 2022-01-25 11:29:27 -05:00
lady ada
96609309e7 add flux capture on any pin with a TC 2022-01-25 02:12:15 -05:00
Limor "Ladyada" Fried
a108bb53ed
Merge pull request #9 from adafruit/allow-extra-rev
Allow an additional revolution to pick up straggling sectors
2022-01-24 18:31:16 -05:00
e36a6127b9
Allow an additional revolution to pick up straggling sectors
The performance on non-overclocked RP2040 from CircuitPython is
marginal, but allowing an additional spindle revolution makes it
>99.9%.

Because the read_track loop _usually_ terminates as soon as all sectors
are successfully read, this doesn't decrease read speed unless a
sector CRC was missed on the first read.
2022-01-17 16:20:02 -06:00
Jeff Epler
94e3802157
Merge pull request #8 from adafruit/360k
360k
2022-01-17 10:58:40 -06:00
lady ada
dcb4dce9aa try manual insall 2022-01-16 17:02:25 -05:00
lady ada
86c4f88ae0 typo 2022-01-16 17:01:18 -05:00
lady ada
3b6c6d2209 doxyclang 2022-01-16 16:59:52 -05:00
lady ada
3ae01d8052 clang 2022-01-16 16:46:41 -05:00
lady ada
29c23bf0e3 make 5.25" drive happy 2022-01-16 16:46:01 -05:00
lady ada
8001a9ada7 add disk read retries 2022-01-16 16:16:49 -05:00
lady ada
1eb38f2024 not always 80 tracks! 2022-01-16 15:36:20 -05:00
lady ada
21b96c80de ugh nevermind 2022-01-16 15:23:52 -05:00
lady ada
391738218a refactor for mfm floppy type, tested with 1.44 hd 2022-01-16 15:22:19 -05:00
lady ada
42bfa32c7f i dont think this will work but i will try! 2022-01-16 15:12:40 -05:00
lady ada
c53cbf0f7e woops 2022-01-16 15:10:45 -05:00
lady ada
4fc1cca1c2 start refactor for DD disks 2022-01-16 15:06:47 -05:00
lady ada
fdd7c19823 add timeout to sleep the drive 2022-01-16 13:49:51 -05:00
lady ada
4c962658b0 add secondary fluxop output for RPM measurements. add setpin for density. fix fluxengine bug. 2022-01-16 12:46:14 -05:00
lady ada
d31d470c2b fixcompile 2022-01-16 12:05:43 -05:00
Jeff Epler
a00e78b871
Merge pull request #7 from adafruit/mfm
add FAT filesystem accessor
2022-01-14 13:42:31 -06:00
lady ada
749c1a03cf and the standalone 2022-01-14 10:48:59 -05:00
lady ada
4a32d92d75 move things in prep for moar files 2022-01-14 10:48:05 -05:00
lady ada
3ec2847a84 get rid of some unused var warnings 2022-01-13 23:42:02 -05:00
lady ada
a82d5f441b add sdfat 2022-01-13 23:21:15 -05:00
lady ada
2cf55cc2d0 Merge branch 'mfm' of github.com:adafruit/Adafruit_Floppy into mfm 2022-01-13 23:13:12 -05:00
lady ada
f0686b9aed lets add sdfat support, and wrap mfm type floppies into an object 2022-01-13 23:13:08 -05:00
Limor "Ladyada" Fried
d5d34127db
Merge pull request #6 from adafruit/mfm
Add real-time MFM decoder
2022-01-13 16:00:29 -05:00
825174e288
need tinyusb builds 2022-01-13 14:49:40 -06:00
lady ada
fa41a7e9b8 add mass storage implementation 2022-01-13 13:59:56 -05:00
lady ada
9ca7d30046 swap sides to read data sequentially 2022-01-13 12:54:44 -05:00
lady ada
aaee310ec9 Merge branch 'mfm' of github.com:adafruit/Adafruit_Floppy into mfm 2022-01-13 12:50:05 -05:00
lady ada
e6a438b8e2 print ascii outtput for tracks 2022-01-13 12:49:59 -05:00
347cfb5982
run clang-format 2022-01-13 11:44:49 -06:00
222c6ce0d7
Fix some signedness warnings 2022-01-13 11:24:24 -06:00
c71c59de49
Fix some rp2040 build errors 2022-01-13 11:24:20 -06:00
Jeff Epler
6c53113d7e
Finish wiring up the MFM real-time decoder 2022-01-13 10:50:02 -06:00
Jeff Epler
5b021bb35a Merge remote-tracking branch 'libmfm/main' 2022-01-13 09:23:27 -06:00
c6b57c3007
improve readme & tidy code 2022-01-12 08:28:35 -06:00
08ec69bcc8
Make it work 2022-01-11 15:45:27 -06:00
9d5136fced
initial commit 2022-01-11 10:05:16 -06:00
30 changed files with 6954 additions and 810 deletions

View file

@ -4,6 +4,10 @@ on: [pull_request, push, repository_dispatch]
jobs:
build:
strategy:
fail-fast: false
matrix:
arduino-platform: ["feather_m4_express_tinyusb", "feather_rp2040_tinyusb"]
runs-on: ubuntu-latest
steps:
@ -19,8 +23,63 @@ 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@v2
with:
name: ${{ github.event.repository.name }}-${{ matrix.arduino-platform }}
path: |
build/*.uf2
- name: Zip release files
if: startsWith(github.ref, 'refs/tags/')
run: |
if [ -d build ]; then
(
echo "Built from Adafruit Floppy `git describe --tags` for ${{ matrix.arduino-platform }}"
echo "Source code: https://github.com/adafruit/Adafruit_Floppy"
echo "Adafruit Learning System: https://learn.adafruit.com/"
) > build/README.txt
cd build && zip -9 -o ${{ matrix.arduino-platform }}.zip *.hex *.bin *.uf2 *.txt
fi
- name: Create release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: build/${{ matrix.arduino-platform }}.zip
fail_on_unmatched_files: false
body: "Select the zip file corresponding to your board from the list below."
doxyclang:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v1
with:
python-version: '3.x'
- uses: actions/checkout@v2
- uses: actions/checkout@v2
with:
repository: adafruit/ci-arduino
path: ci
- name: pre-install
run: bash ci/actions_install.sh
- name: fix SDFat
run: git clone --quiet https://github.com/adafruit/SdFat.git /home/runner/Arduino/libraries/SdFat
- name: clang
run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r .

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries
#
# SPDX-License-Identifier: MIT
/mfm
/html
/ci
/examples/*/build

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "doxygen-awesome-css"]
path = doxygen-awesome-css
url = https://github.com/jothepro/doxygen-awesome-css.git

View file

@ -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]);
}
}
}
}

View file

@ -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

2458
Doxyfile Normal file

File diff suppressed because it is too large Load diff

156
LICENSES/CC-BY-4.0.txt Normal file
View 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 licensors permission is not necessary for any reasonfor example, because of any applicable exception or limitation to copyrightthen 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
View 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
View 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.

1
doxygen-awesome-css Submodule

@ -0,0 +1 @@
Subproject commit 4cd62308d825fe0396d2f66ffbab45d0e247724c

View file

@ -0,0 +1,76 @@
#include <Adafruit_Floppy.h>
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
#define ENABLE_PIN (6)
#define PHASE1_PIN (A2)
#define PHASE2_PIN (13)
#define PHASE3_PIN (12)
#define PHASE4_PIN (11)
#define RDDATA_PIN (5)
#define INDEX_PIN (A3)
#define APPLE2_PROTECT_PIN (21) // "SDA"
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
#define ENABLE_PIN (8) // D6
#define PHASE1_PIN (A2)
#define PHASE2_PIN (13)
#define PHASE3_PIN (12)
#define PHASE4_PIN (11)
#define RDDATA_PIN (7) // D5
#define INDEX_PIN (A3)
#define APPLE2_PROTECT_PIN (2) // "SDA"
#else
#error "Please set up pin definitions!"
#endif
Adafruit_Apple2Floppy floppy(INDEX_PIN, ENABLE_PIN,
PHASE1_PIN, PHASE2_PIN, PHASE3_PIN, PHASE4_PIN,
-1, -1, APPLE2_PROTECT_PIN, RDDATA_PIN);
// WARNING! there are 150K max flux pulses per track!
uint8_t flux_transitions[MAX_FLUX_PULSE_PER_TRACK];
uint32_t time_stamp = 0;
void setup() {
Serial.begin(115200);
while (!Serial) delay(100);
Serial.println("its time for a nice floppy transfer!");
floppy.debug_serial = &Serial;
if (!floppy.begin()) {
Serial.println("Failed to initialize floppy interface");
while (1) yield();
}
floppy.select(true);
if (! floppy.spin_motor(true)) {
Serial.println("Failed to spin up motor & find index pulse");
while (1) yield();
}
Serial.print("Seeking track...");
if (! floppy.goto_track(0)) {
Serial.println("Failed to seek to track");
while (1) yield();
}
Serial.println("done!");
}
void loop() {
int32_t index_pulse_offset;
uint32_t captured_flux = floppy.capture_track(flux_transitions, sizeof(flux_transitions), &index_pulse_offset, true);
Serial.print("Captured ");
Serial.print(captured_flux);
Serial.println(" flux transitions");
//floppy.print_pulses(flux_transitions, captured_flux);
floppy.print_pulse_bins(flux_transitions, captured_flux, 255, true);
Serial.printf("Write protect: %s\n", floppy.get_write_protect() ? "ON" : "off");
delay(100);
}

View file

@ -0,0 +1,148 @@
/*
Print size, modify date/time, and name for all files in root.
*/
/*********************************************************************
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
*********************************************************************/
#include <SPI.h>
#include "SdFat.h"
#include <Adafruit_Floppy.h>
// If using SAMD51, turn on TINYUSB USB stack
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
#define DENSITY_PIN A0 // IDC 2
#define INDEX_PIN A1 // IDC 8
#define SELECT_PIN A2 // IDC 12
#define MOTOR_PIN A3 // IDC 16
#define DIR_PIN A4 // IDC 18
#define STEP_PIN A5 // IDC 20
#define WRDATA_PIN 13 // IDC 22 (not used during read)
#define WRGATE_PIN 12 // IDC 24 (not used during read)
#define TRK0_PIN 11 // IDC 26
#define PROT_PIN 10 // IDC 28
#define READ_PIN 9 // IDC 30
#define SIDE_PIN 6 // IDC 32
#define READY_PIN 5 // IDC 34
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
#define DENSITY_PIN A0 // IDC 2
#define INDEX_PIN A1 // IDC 8
#define SELECT_PIN A2 // IDC 12
#define MOTOR_PIN A3 // IDC 16
#define DIR_PIN 24 // IDC 18
#define STEP_PIN 25 // IDC 20
#define WRDATA_PIN 13 // IDC 22 (not used during read)
#define WRGATE_PIN 12 // IDC 24 (not used during read)
#define TRK0_PIN 11 // IDC 26
#define PROT_PIN 10 // IDC 28
#define READ_PIN 9 // IDC 30
#define SIDE_PIN 8 // IDC 32
#define READY_PIN 7 // IDC 34
#elif defined (ARDUINO_RASPBERRY_PI_PICO)
#define DENSITY_PIN 2 // IDC 2
#define INDEX_PIN 3 // IDC 8
#define SELECT_PIN 4 // IDC 12
#define MOTOR_PIN 5 // IDC 16
#define DIR_PIN 6 // IDC 18
#define STEP_PIN 7 // IDC 20
#define WRDATA_PIN 8 // IDC 22 (not used during read)
#define WRGATE_PIN 9 // IDC 24 (not used during read)
#define TRK0_PIN 10 // IDC 26
#define PROT_PIN 11 // IDC 28
#define READ_PIN 12 // IDC 30
#define SIDE_PIN 13 // IDC 32
#define READY_PIN 14 // IDC 34
#else
#error "Please set up pin definitions!"
#endif
Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
MOTOR_PIN, DIR_PIN, STEP_PIN,
WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
Adafruit_MFM_Floppy mfm_floppy(&floppy);
// file system object from SdFat
FatFileSystem fatfs;
FatFile root;
FatFile file;
//------------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
// Wait for USB Serial
while (!Serial) {
SysCall::yield();
}
Serial.println("Floppy FAT directory listing demo");
// Init floppy drive - must spin up and find index
if (! mfm_floppy.begin()) {
Serial.println("Floppy didn't initialize - check wiring and diskette!");
}
// Init file system on the flash
fatfs.begin(&mfm_floppy);
if (!root.open("/")) {
Serial.println("open root failed");
}
// Open next file in root.
// Warning, openNext starts at the current directory position
// so a rewind of the directory may be required.
while (file.openNext(&root, O_RDONLY)) {
file.printFileSize(&Serial);
Serial.write(' ');
file.printModifyDateTime(&Serial);
Serial.write(' ');
file.printName(&Serial);
if (file.isDir()) {
// Indicate a directory.
Serial.write('/');
}
Serial.println();
file.close();
}
if (root.getError()) {
Serial.println("openNext failed");
} else {
Serial.println("Done!");
}
}
//------------------------------------------------------------------------------
void loop() {
Serial.print("Read a file? >");
String filename;
do {
filename = Serial.readStringUntil('\n');
filename.trim();
} while (filename.length() == 0);
Serial.print("Reading file name: ");
Serial.println(filename);
// Open the file for reading and check that it was successfully opened.
// The FILE_READ mode will open the file for reading.
File dataFile = fatfs.open(filename, FILE_READ);
if (!dataFile) {
Serial.println("Failed to open data file! Does it exist?");
return;
}
// File was opened, now print out data character by character until at the
// end of the file.
Serial.println("Opened file, printing contents below:");
while (dataFile.available()) {
// Use the read function to read the next character.
// You can alternatively use other functions like readUntil, readString, etc.
// See the fatfs_full_usage example for more details.
char c = dataFile.read();
Serial.print(c);
}
}

View file

@ -2,56 +2,47 @@
// 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
#define DENSITY_PIN A0 // IDC 2
#define INDEX_PIN A1 // IDC 8
#define SELECT_PIN A2 // IDC 12
#define MOTOR_PIN A3 // IDC 16
#define DIR_PIN A4 // IDC 18
#define STEP_PIN A5 // IDC 20
#define WRDATA_PIN 13 // IDC 22 (not used during read)
#define WRGATE_PIN 12 // IDC 24 (not used during read)
#define TRK0_PIN 11 // IDC 26
#define PROT_PIN 10 // IDC 28
#define READ_PIN 9 // IDC 30
#define SIDE_PIN 6 // IDC 32
#define READY_PIN 5 // IDC 34
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
#define DENSITY_PIN A0 // IDC 2
#define INDEX_PIN A1 // IDC 8
#define SELECT_PIN A2 // IDC 12
#define MOTOR_PIN A3 // IDC 16
#define DIR_PIN 24 // IDC 18
#define STEP_PIN 25 // IDC 20
#define WRDATA_PIN 13 // IDC 22 (not used during read)
#define WRGATE_PIN 12 // IDC 24 (not used during read)
#define TRK0_PIN 11 // IDC 26
#define PROT_PIN 10 // IDC 28
#define READ_PIN 9 // IDC 30
#define SIDE_PIN 8 // IDC 32
#define READY_PIN 7 // IDC 34
#if F_CPU != 200000000L
#warning "please set CPU speed to 200MHz overclock"
#endif
#define DENSITY_PIN A0 // IDC 2
#define INDEX_PIN A1 // IDC 8
#define SELECT_PIN A2 // IDC 12
#define MOTOR_PIN A3 // IDC 16
#define DIR_PIN 24 // IDC 18
#define STEP_PIN 25 // IDC 20
#define WRDATA_PIN 13 // IDC 22 (not used during read)
#define WRGATE_PIN 12 // IDC 24 (not used during read)
#define TRK0_PIN 11 // IDC 26
#define PROT_PIN 10 // IDC 28
#define READ_PIN 9 // IDC 30
#define SIDE_PIN 6 // IDC 32
#define READY_PIN 5 // IDC 34
#elif defined (ARDUINO_RASPBERRY_PI_PICO)
#define DENSITY_PIN 2 // IDC 2
#define INDEX_PIN 3 // IDC 8
#define SELECT_PIN 4 // IDC 12
#define MOTOR_PIN 5 // IDC 16
#define DIR_PIN 6 // IDC 18
#define STEP_PIN 7 // IDC 20
#define WRDATA_PIN 8 // IDC 22 (not used during read)
#define WRGATE_PIN 9 // IDC 24 (not used during read)
#define TRK0_PIN 10 // IDC 26
#define PROT_PIN 11 // IDC 28
#define READ_PIN 12 // IDC 30
#define SIDE_PIN 13 // IDC 32
#define READY_PIN 14 // IDC 34
#if F_CPU != 200000000L
#warning "please set CPU speed to 200MHz overclock"
#endif
#define DENSITY_PIN 2 // IDC 2
#define INDEX_PIN 3 // IDC 8
#define SELECT_PIN 4 // IDC 12
#define MOTOR_PIN 5 // IDC 16
#define DIR_PIN 6 // IDC 18
#define STEP_PIN 7 // IDC 20
#define WRDATA_PIN 8 // IDC 22 (not used during read)
#define WRGATE_PIN 9 // IDC 24 (not used during read)
#define TRK0_PIN 10 // IDC 26
#define PROT_PIN 11 // IDC 28
#define READ_PIN 12 // IDC 30
#define SIDE_PIN 13 // IDC 32
#define READY_PIN 14 // IDC 34
#else
#error "Please set up pin definitions!"
#endif
@ -73,7 +64,11 @@ void setup() {
Serial.println("its time for a nice floppy transfer!");
floppy.debug_serial = &Serial;
floppy.begin();
if (!floppy.begin()) {
Serial.println("Failed to initialize floppy interface");
while (1) yield();
}
floppy.select(true);
if (! floppy.spin_motor(true)) {
@ -90,20 +85,21 @@ void setup() {
}
void loop() {
uint32_t captured_flux = floppy.capture_track(flux_transitions, sizeof(flux_transitions));
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);
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.println(floppy.get_write_protect() ? "Yes" : "No");
Serial.print("Track 0? ");
Serial.println(digitalRead(TRK0_PIN) ? "No" : "Yes");
time_stamp = millis();

View file

@ -0,0 +1,129 @@
#include <Adafruit_Floppy.h>
// If using SAMD51, turn on TINYUSB USB stack
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
#define DENSITY_PIN A0 // IDC 2
#define INDEX_PIN A1 // IDC 8
#define SELECT_PIN A2 // IDC 12
#define MOTOR_PIN A3 // IDC 16
#define DIR_PIN A4 // IDC 18
#define STEP_PIN A5 // IDC 20
#define WRDATA_PIN 13 // IDC 22 (not used during read)
#define WRGATE_PIN 12 // IDC 24 (not used during read)
#define TRK0_PIN 11 // IDC 26
#define PROT_PIN 10 // IDC 28
#define READ_PIN 9 // IDC 30
#define SIDE_PIN 6 // IDC 32
#define READY_PIN 5 // IDC 34
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
#define DENSITY_PIN A0 // IDC 2
#define INDEX_PIN A1 // IDC 8
#define SELECT_PIN A2 // IDC 12
#define MOTOR_PIN A3 // IDC 16
#define DIR_PIN 24 // IDC 18
#define STEP_PIN 25 // IDC 20
#define WRDATA_PIN 13 // IDC 22 (not used during read)
#define WRGATE_PIN 12 // IDC 24 (not used during read)
#define TRK0_PIN 11 // IDC 26
#define PROT_PIN 10 // IDC 28
#define READ_PIN 9 // IDC 30
#define SIDE_PIN 6 // IDC 32
#define READY_PIN 5 // IDC 34
#elif defined (ARDUINO_RASPBERRY_PI_PICO)
#define DENSITY_PIN 2 // IDC 2
#define INDEX_PIN 3 // IDC 8
#define SELECT_PIN 4 // IDC 12
#define MOTOR_PIN 5 // IDC 16
#define DIR_PIN 6 // IDC 18
#define STEP_PIN 7 // IDC 20
#define WRDATA_PIN 8 // IDC 22 (not used during read)
#define WRGATE_PIN 9 // IDC 24 (not used during read)
#define TRK0_PIN 10 // IDC 26
#define PROT_PIN 11 // IDC 28
#define READ_PIN 12 // IDC 30
#define SIDE_PIN 13 // IDC 32
#define READY_PIN 14 // IDC 34
#else
#error "Please set up pin definitions!"
#endif
Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
MOTOR_PIN, DIR_PIN, STEP_PIN,
WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
// WARNING! there are 150K max flux pulses per track!
uint8_t flux_transitions[MAX_FLUX_PULSE_PER_TRACK];
uint32_t time_stamp = 0;
void setup() {
Serial.begin(115200);
while (!Serial) delay(100);
Serial.println("its time for a nice floppy transfer!");
floppy.debug_serial = &Serial;
if (!floppy.begin()) {
Serial.println("Failed to initialize floppy interface");
while (1) yield();
}
floppy.select(true);
if (! floppy.spin_motor(true)) {
Serial.println("Failed to spin up motor & find index pulse");
while (1) yield();
}
Serial.print("Seeking track...");
if (! floppy.goto_track(0)) {
Serial.println("Failed to seek to track");
while (1) yield();
}
Serial.println("done!");
}
void loop() {
int32_t index_pulse_offset;
uint32_t captured_flux = floppy.capture_track(flux_transitions, sizeof(flux_transitions), &index_pulse_offset, true);
Serial.print("Captured ");
Serial.print(captured_flux);
Serial.println(" flux transitions");
//floppy.print_pulses(flux_transitions, captured_flux);
floppy.print_pulse_bins(flux_transitions, captured_flux, 255, true);
if ((millis() - time_stamp) > 1000) {
Serial.print("Ready? ");
Serial.println(digitalRead(READY_PIN) ? "No" : "Yes");
Serial.print("Write Protected? ");
Serial.println(digitalRead(PROT_PIN) ? "No" : "Yes");
Serial.print("Track 0? ");
Serial.println(digitalRead(TRK0_PIN) ? "No" : "Yes");
time_stamp = millis();
}
unsigned T_2 = floppy.getSampleFrequency() * 2 / 1000000;
unsigned T_3 = floppy.getSampleFrequency() * 3 / 1000000;
unsigned T_4 = floppy.getSampleFrequency() * 4 / 1000000;
for (size_t i = 0; i < sizeof(flux_transitions); i += 3) {
flux_transitions[i] = T_2;
}
for (size_t i = 1; i < sizeof(flux_transitions); i += 3) {
flux_transitions[i] = T_3;
}
for (size_t i = 2; i < sizeof(flux_transitions); i += 3) {
flux_transitions[i] = T_4;
}
floppy.print_pulse_bins(flux_transitions, captured_flux, 255, true);
Serial.println("Writing track with T234234...");
Serial.printf("T2 = %d T3 = %d T4 = %d\n", T_2, T_3, T_4);
floppy.write_track(flux_transitions, sizeof(flux_transitions), true);
yield();
}

View file

@ -1,67 +1,87 @@
#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
#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
#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"
#define DENSITY_PIN A0 // IDC 2
#define INDEX_PIN A1 // IDC 8
#define SELECT_PIN A2 // IDC 12
#define MOTOR_PIN A3 // IDC 16
#define DIR_PIN 24 // IDC 18
#define STEP_PIN 25 // IDC 20
#define WRDATA_PIN 13 // IDC 22
#define WRGATE_PIN 12 // IDC 24
#define TRK0_PIN 11 // IDC 26
#define PROT_PIN 10 // IDC 28
#define READ_PIN 9 // IDC 30
#define SIDE_PIN 8 // IDC 32
#define READY_PIN 7 // IDC 34
// jepler's prototype board, subject to change
#define APPLE2_PHASE1_PIN (A2) // IDC 2
#define APPLE2_PHASE2_PIN (13) // IDC 4
#define APPLE2_PHASE3_PIN (12) // IDC 6
#define APPLE2_PHASE4_PIN (11) // IDC 8
#define APPLE2_WRGATE_PIN (10) // IDC 10
#define APPLE2_ENABLE_PIN (8) // D6 // IDC 14
#define APPLE2_RDDATA_PIN (7) // D5 // IDC 16
#define APPLE2_WRDATA_PIN (3) // SCL // IDC 18
#define APPLE2_PROTECT_PIN (2) // SDA // IDC 20
#define APPLE2_INDEX_PIN (A3)
#ifndef USE_TINYUSB
#error "Please set Adafruit TinyUSB under Tools > USB Stack"
#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"
#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
#define GW_SAMPLEFREQ 26000000UL // 26mhz for rp2040
#else
#error "Please set up pin definitions!"
#endif
Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
#ifdef READ_PIN
Adafruit_Floppy pcfloppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
MOTOR_PIN, DIR_PIN, STEP_PIN,
WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
#else
#warning "This firmware will not support PC/Shugart drives"
#endif
#ifdef APPLE2_RDDATA_PIN
Adafruit_Apple2Floppy apple2floppy(APPLE2_INDEX_PIN, APPLE2_ENABLE_PIN,
APPLE2_PHASE1_PIN, APPLE2_PHASE2_PIN, APPLE2_PHASE3_PIN, APPLE2_PHASE4_PIN,
APPLE2_WRDATA_PIN, APPLE2_WRGATE_PIN, APPLE2_PROTECT_PIN, APPLE2_RDDATA_PIN);
#else
#warning "This firmware will not support Apple ][ drives"
#endif
Adafruit_FloppyBase *floppy;
uint32_t time_stamp = 0;
@ -85,23 +105,65 @@ uint8_t cmd_buff_idx = 0;
#define GW_CMD_GETPARAMS_DELAYS 0
#define GW_CMD_MOTOR 6
#define GW_CMD_READFLUX 7
#define GW_CMD_WRITEFLUX 8
#define GW_CMD_GETFLUXSTATUS 9
#define GW_CMD_SELECT 12
#define GW_CMD_DESELECT 13
#define GW_CMD_SETBUSTYPE 14
#define GW_CMD_SETBUSTYPE_IBM 1
#define GW_CMD_SETBUSTYPE_SHUGART 2
#define GW_CMD_SETBUSTYPE_APPLE2 3
#define GW_CMD_SETBUSTYPE_APPLE2_QUARTERTRACK 4
#define GW_CMD_SETPIN 15
#define GW_CMD_SETPIN_DENSITY 2
#define GW_CMD_RESET 16
#define GW_CMD_SOURCEBYTES 18
#define GW_CMD_SINKBYTES 19
#define GW_CMD_GETPIN 20
#define GW_CMD_GETPIN_TRACK0 26
#define GW_ACK_OK (byte)0
#define GW_ACK_BADCMD 1
#define GW_ACK_NOINDEX 2
#define GW_ACK_NOTRACK0 3
#define GW_ACK_WRPROT 6
#define GW_ACK_NOUNIT 7
#define GW_ACK_BADPIN 10
uint32_t timestamp = 0;
bool setbustype(int bustype) {
if (floppy) {
floppy->end();
}
floppy = nullptr;
switch (bustype) {
case -1:
#ifdef READ_PIN
case GW_CMD_SETBUSTYPE_IBM:
case GW_CMD_SETBUSTYPE_SHUGART:
// TODO: whats the diff???
floppy = &pcfloppy;
break;
#endif
#ifdef APPLE2_RDDATA_PIN
case GW_CMD_SETBUSTYPE_APPLE2:
floppy = &apple2floppy;
apple2floppy.step_mode(Adafruit_Apple2Floppy::STEP_MODE_HALF);
break;
case GW_CMD_SETBUSTYPE_APPLE2_QUARTERTRACK:
apple2floppy.step_mode(Adafruit_Apple2Floppy::STEP_MODE_QUARTER);
floppy = &apple2floppy;
break;
#endif
default:
Serial1.printf("Unsupported bus type %d\n", bustype);
return false;
}
floppy->debug_serial = &Serial1;
auto result = floppy->begin();
Serial1.printf("setbustype() floppy = %p result=%d\n", floppy, result);
return result;
}
void setup() {
Serial.begin(115200);
@ -109,12 +171,12 @@ void setup() {
//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 +186,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 +198,39 @@ 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];
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, cmd_buffer[1]);
if (cmd == GW_CMD_GETINFO) {
Serial1.println("Get info");
@ -158,10 +241,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 +286,47 @@ void loop() {
uint8_t sub_cmd = cmd_buffer[2];
if (sub_cmd == GW_CMD_GETPARAMS_DELAYS) {
reply_buffer[i++] = GW_ACK_OK;
reply_buffer[i++] = floppy.select_delay_us & 0xFF;
reply_buffer[i++] = floppy.select_delay_us >> 8;
reply_buffer[i++] = floppy.step_delay_us & 0xFF;
reply_buffer[i++] = floppy.step_delay_us >> 8;
reply_buffer[i++] = floppy.settle_delay_ms & 0xFF;
reply_buffer[i++] = floppy.settle_delay_ms >> 8;
reply_buffer[i++] = floppy.motor_delay_ms & 0xFF;
reply_buffer[i++] = floppy.motor_delay_ms >> 8;
reply_buffer[i++] = floppy.watchdog_delay_ms & 0xFF;
reply_buffer[i++] = floppy.watchdog_delay_ms >> 8;
reply_buffer[i++] = floppy->select_delay_us & 0xFF;
reply_buffer[i++] = floppy->select_delay_us >> 8;
reply_buffer[i++] = floppy->step_delay_us & 0xFF;
reply_buffer[i++] = floppy->step_delay_us >> 8;
reply_buffer[i++] = floppy->settle_delay_ms & 0xFF;
reply_buffer[i++] = floppy->settle_delay_ms >> 8;
reply_buffer[i++] = floppy->motor_delay_ms & 0xFF;
reply_buffer[i++] = floppy->motor_delay_ms >> 8;
reply_buffer[i++] = floppy->watchdog_delay_ms & 0xFF;
reply_buffer[i++] = floppy->watchdog_delay_ms >> 8;
Serial.write(reply_buffer, 12);
}
}
else if (cmd == GW_CMD_RESET) {
Serial1.println("Soft reset");
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) {
if (!floppy) goto needfloppy;
uint8_t track = cmd_buffer[2];
Serial1.printf("Seek track %d\n\r", track);
bool r = floppy.goto_track(track);
bool r = floppy->goto_track(track);
if (r) {
reply_buffer[i++] = GW_ACK_OK;
} else {
@ -252,45 +336,62 @@ 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)) {
if (motor_state != state) { // we're in the opposite state
if (! floppy->spin_motor(state)) {
reply_buffer[i++] = GW_ACK_NOINDEX;
} else {
reply_buffer[i++] = GW_ACK_OK;
}
motor_state = state;
} else {
// our cached state is correct!
reply_buffer[i++] = GW_ACK_OK;
}
Serial.write(reply_buffer, 2);
}
else if (cmd == GW_CMD_SELECT) {
if (!floppy) goto needfloppy;
uint8_t sub_cmd = cmd_buffer[2];
Serial1.printf("Select drive %d\n\r", sub_cmd);
if (sub_cmd == 0) {
floppy.select(true);
floppy->select(true);
reply_buffer[i++] = GW_ACK_OK;
} else {
reply_buffer[i++] = GW_ACK_NOUNIT;
}
Serial1.printf("Reply buffer = %d %d\n", reply_buffer[0], reply_buffer[1]);
Serial.write(reply_buffer, 2);
}
else if (cmd == GW_CMD_DESELECT) {
if (!floppy) goto needfloppy;
Serial1.printf("Deselect drive\n\r");
floppy.select(false);
floppy->select(false);
reply_buffer[i++] = GW_ACK_OK;
Serial.write(reply_buffer, 2);
}
else if (cmd == GW_CMD_READFLUX) {
if (!floppy) goto needfloppy;
uint32_t flux_ticks;
uint16_t revs;
flux_ticks = cmd_buffer[5];
@ -303,31 +404,73 @@ void loop() {
revs = cmd_buffer[7];
revs <<= 8;
revs |= cmd_buffer[6];
if (revs) {
revs -= 1;
}
if (floppy->track() == -1) {
floppy->goto_track(0);
}
Serial1.printf("Reading flux0rs on track %d: %u ticks and %d revs\n\r", floppy->track(), flux_ticks, revs);
Serial1.printf("Sample freqency %.1fMHz\n", floppy->getSampleFrequency() / 1e6);
uint16_t capture_ms = 0;
uint16_t capture_revs = 0;
if (flux_ticks) {
// we'll back calculate the revolutions
capture_ms = 1000.0 * (float)flux_ticks / (float)floppy->getSampleFrequency();
revs = 1;
} else {
capture_revs = revs;
capture_ms = 0;
}
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);
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 +479,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);
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 +607,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 +627,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!");
}

View file

@ -0,0 +1,129 @@
#include <Adafruit_Floppy.h>
// If using SAMD51, turn on TINYUSB USB stack
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
#define DENSITY_PIN A0 // IDC 2
#define INDEX_PIN A1 // IDC 8
#define SELECT_PIN A2 // IDC 12
#define MOTOR_PIN A3 // IDC 16
#define DIR_PIN A4 // IDC 18
#define STEP_PIN A5 // IDC 20
#define WRDATA_PIN 13 // IDC 22 (not used during read)
#define WRGATE_PIN 12 // IDC 24 (not used during read)
#define TRK0_PIN 11 // IDC 26
#define PROT_PIN 10 // IDC 28
#define READ_PIN 9 // IDC 30
#define SIDE_PIN 6 // IDC 32
#define READY_PIN 5 // IDC 34
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
#define DENSITY_PIN A0 // IDC 2
#define INDEX_PIN A1 // IDC 8
#define SELECT_PIN A2 // IDC 12
#define MOTOR_PIN A3 // IDC 16
#define DIR_PIN 24 // IDC 18
#define STEP_PIN 25 // IDC 20
#define WRDATA_PIN 13 // IDC 22 (not used during read)
#define WRGATE_PIN 12 // IDC 24 (not used during read)
#define TRK0_PIN 11 // IDC 26
#define PROT_PIN 10 // IDC 28
#define READ_PIN 9 // IDC 30
#define SIDE_PIN 8 // IDC 32
#define READY_PIN 7 // IDC 34
#elif defined (ARDUINO_RASPBERRY_PI_PICO)
#define DENSITY_PIN 2 // IDC 2
#define INDEX_PIN 3 // IDC 8
#define SELECT_PIN 4 // IDC 12
#define MOTOR_PIN 5 // IDC 16
#define DIR_PIN 6 // IDC 18
#define STEP_PIN 7 // IDC 20
#define WRDATA_PIN 8 // IDC 22 (not used during read)
#define WRGATE_PIN 9 // IDC 24 (not used during read)
#define TRK0_PIN 10 // IDC 26
#define PROT_PIN 11 // IDC 28
#define READ_PIN 12 // IDC 30
#define SIDE_PIN 13 // IDC 32
#define READY_PIN 14 // IDC 34
#else
#error "Please set up pin definitions!"
#endif
Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
MOTOR_PIN, DIR_PIN, STEP_PIN,
WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
// You can select IBMPC1440K or IBMPC360K (check adafruit_floppy_disk_t options!)
Adafruit_MFM_Floppy mfm_floppy(&floppy, IBMPC360K);
uint32_t time_stamp = 0;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(115200);
while (!Serial) delay(100);
delay(500); // wait for serial to open
Serial.println("its time for a nice floppy transfer!");
floppy.debug_serial = &Serial;
if (! mfm_floppy.begin()) {
Serial.println("Failed to spin up motor & find index pulse");
while (1) yield();
}
}
uint8_t track = 0;
bool head = 0;
void loop() {
int32_t captured_sectors;
Serial.printf("Seeking track %d head %d\n", track, head);
captured_sectors = mfm_floppy.readTrack(track, head);
if (captured_sectors < 0) {
Serial.println("Failed to seek to track");
while (1) yield();
}
Serial.printf("Captured %d sectors\n", captured_sectors);
Serial.print("Validity: ");
for (size_t i = 0; i < mfm_floppy.sectors_per_track(); i++) {
Serial.print(mfm_floppy.track_validity[i] ? "V" : "?");
}
Serial.print("\n");
for (size_t sector = 0; sector < mfm_floppy.sectors_per_track(); sector++) {
if (!mfm_floppy.track_validity[sector]) {
continue; // skip it, not valid
}
for (size_t i = 0; i < 512; i += 16) {
size_t addr = sector * 512 + i;
Serial.printf("%08x", addr);
for (size_t j = 0; j < 16; j++) {
Serial.printf(" %02x", mfm_floppy.track_data[addr + j]);
}
Serial.print(" | ");
for (size_t j = 0; j < 16; j++) {
uint8_t d = mfm_floppy.track_data[addr + j];
if (! isprint(d)) {
d = ' ';
}
Serial.write(d);
}
Serial.print("\n");
}
}
// advance to next track
if (!head) { // we were on side 0
head = 1; // go to side 1
} else { // we were on side 1?
track = (track + 1) % mfm_floppy.tracks_per_side(); // next track!
head = 0; // and side 0
}
delay(1000);
}

View file

@ -0,0 +1,154 @@
// this example makes a lot of assumptions: MFM floppy which is already inserted
// and only reading is supported - no write yet!
#include <Adafruit_Floppy.h>
#include "Adafruit_TinyUSB.h"
Adafruit_USBD_MSC usb_msc;
// If using SAMD51, turn on TINYUSB USB stack
#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
#define DENSITY_PIN A0 // IDC 2
#define INDEX_PIN A1 // IDC 8
#define SELECT_PIN A2 // IDC 12
#define MOTOR_PIN A3 // IDC 16
#define DIR_PIN A4 // IDC 18
#define STEP_PIN A5 // IDC 20
#define WRDATA_PIN 13 // IDC 22 (not used during read)
#define WRGATE_PIN 12 // IDC 24 (not used during read)
#define TRK0_PIN 11 // IDC 26
#define PROT_PIN 10 // IDC 28
#define READ_PIN 9 // IDC 30
#define SIDE_PIN 6 // IDC 32
#define READY_PIN 5 // IDC 34
#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
#define DENSITY_PIN A0 // IDC 2
#define INDEX_PIN A1 // IDC 8
#define SELECT_PIN A2 // IDC 12
#define MOTOR_PIN A3 // IDC 16
#define DIR_PIN 24 // IDC 18
#define STEP_PIN 25 // IDC 20
#define WRDATA_PIN 13 // IDC 22 (not used during read)
#define WRGATE_PIN 12 // IDC 24 (not used during read)
#define TRK0_PIN 11 // IDC 26
#define PROT_PIN 10 // IDC 28
#define READ_PIN 9 // IDC 30
#define SIDE_PIN 8 // IDC 32
#define READY_PIN 7 // IDC 34
#elif defined (ARDUINO_RASPBERRY_PI_PICO)
#define DENSITY_PIN 2 // IDC 2
#define INDEX_PIN 3 // IDC 8
#define SELECT_PIN 4 // IDC 12
#define MOTOR_PIN 5 // IDC 16
#define DIR_PIN 6 // IDC 18
#define STEP_PIN 7 // IDC 20
#define WRDATA_PIN 8 // IDC 22 (not used during read)
#define WRGATE_PIN 9 // IDC 24 (not used during read)
#define TRK0_PIN 10 // IDC 26
#define PROT_PIN 11 // IDC 28
#define READ_PIN 12 // IDC 30
#define SIDE_PIN 13 // IDC 32
#define READY_PIN 14 // IDC 34
#else
#error "Please set up pin definitions!"
#endif
Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
MOTOR_PIN, DIR_PIN, STEP_PIN,
WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
// You can select IBMPC1440K or IBMPC360K (check adafruit_floppy_disk_t options!)
Adafruit_MFM_Floppy mfm_floppy(&floppy, IBMPC1440K);
constexpr size_t SECTOR_SIZE = 512UL;
int8_t last_track_read = -1; // last cached track
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(115200);
#if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040)
// Manual begin() is required on core without built-in support for TinyUSB such as
// - mbed rp2040
TinyUSB_Device_Init(0);
#endif
// Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively
usb_msc.setID("Adafruit", "Floppy Mass Storage", "1.0");
// Set disk size
usb_msc.setCapacity(mfm_floppy.sectors_per_track() * mfm_floppy.tracks_per_side() * FLOPPY_HEADS, SECTOR_SIZE);
// Set callback
usb_msc.setReadWriteCallback(msc_read_callback, msc_write_callback, msc_flush_callback);
floppy.debug_serial = &Serial;
floppy.begin();
// Set Lun ready
usb_msc.setUnitReady(true);
Serial.println("Ready!");
usb_msc.begin();
if (! mfm_floppy.begin()) {
Serial.println("Failed to spin up motor & find index pulse");
while (1) yield();
}
}
void loop() {
delay(1000);
}
// Callback invoked when received READ10 command.
// Copy disk's data to buffer (up to bufsize) and
// return number of copied bytes (must be multiple of block size)
int32_t msc_read_callback (uint32_t lba, void* buffer, uint32_t bufsize)
{
Serial.printf("read call back block %d size %d\n", lba, bufsize);
uint8_t track = lba / (2 * mfm_floppy.sectors_per_track());
uint8_t head = (lba / mfm_floppy.sectors_per_track()) % 2;
uint8_t subsector = lba % mfm_floppy.sectors_per_track();
uint8_t retries = 5;
for (int retry = 0; retry < retries; retry++) {
if (((track * 2 + head) == last_track_read) && mfm_floppy.track_validity[subsector]) {
// aah we've got it and its valid!
Serial.println("OK!");
memcpy(buffer, mfm_floppy.track_data + (subsector * SECTOR_SIZE), SECTOR_SIZE);
return SECTOR_SIZE;
}
// ok so either its not valid, or we didn't read this track yet...
int32_t tracks_read = mfm_floppy.readTrack(track, head);
if (tracks_read < 0) {
Serial.println("Failed to seek to track");
return 0;
}
last_track_read = track * 2 + head;
// we'll go again on the next round
}
Serial.println("subsector invalid CRC :(");
return 0;
}
// Callback invoked when received WRITE10 command.
// Process data in buffer to disk's storage and
// return number of written bytes (must be multiple of block size)
int32_t msc_write_callback (uint32_t lba, uint8_t* buffer, uint32_t bufsize)
{
Serial.printf("write call back block %d size %d\n", lba, bufsize);
// we dont actually write yet
return bufsize;
}
// Callback invoked when WRITE10 command is completed (status received and accepted by host).
// used to flush any pending cache.
void msc_flush_callback (void)
{
Serial.println("flush\n");
// nothing to do
}

View file

@ -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

1223
src/Adafruit_Floppy.cpp Normal file

File diff suppressed because it is too large Load diff

326
src/Adafruit_Floppy.h Normal file
View file

@ -0,0 +1,326 @@
#ifndef ADAFRUIT_FLOPPY_H
#define ADAFRUIT_FLOPPY_H
/*! \mainpage
*
* \image html rabbit.png
*
* This is a helper library to abstract away interfacing with floppy disk drives
* in a cross-platform and open source library.
*
* Adafruit Floppy is a project to make a flexible, full-stack, open source
* hardware/software device for reading, archiving, accessing and duplicating
* floppy disk media. It joins a family of open source hardware and software
* such as greaseweazle and fluxengine, and increases the availability and
* accessibility of floppy disk controllers.
*/
#include "Arduino.h"
#include <Adafruit_SPIDevice.h>
// to implement SdFat Block Driver
#include "SdFat.h"
#include "SdFatConfig.h"
#define FLOPPY_IBMPC_HD_TRACKS 80
#define FLOPPY_IBMPC_DD_TRACKS 40
#define FLOPPY_HEADS 2
#define MFM_IBMPC1440K_SECTORS_PER_TRACK 18
#define MFM_IBMPC360K_SECTORS_PER_TRACK 9
#define MFM_BYTES_PER_SECTOR 512UL
#define STEP_OUT HIGH
#define STEP_IN LOW
#define MAX_FLUX_PULSE_PER_TRACK \
(uint32_t)(500000UL / 5 * \
1.5) // 500khz / 5 hz per track rotation, 1.5 rotations
#define BUSTYPE_IBMPC 1
#define BUSTYPE_SHUGART 2
typedef enum {
IBMPC1440K,
IBMPC360K,
} adafruit_floppy_disk_t;
/**************************************************************************/
/*!
@brief An abstract base class for chattin with floppy drives
*/
/**************************************************************************/
class Adafruit_FloppyBase {
protected:
Adafruit_FloppyBase(int indexpin, int wrdatapin, int wrgatepin, int rddatapin,
bool is_apple2 = false);
public:
bool begin(void);
virtual void end();
virtual void soft_reset(void);
/**************************************************************************/
/*!
@brief Whether to select this drive
@param selected True to select/enable
*/
/**************************************************************************/
virtual void select(bool selected) = 0;
/**************************************************************************/
/*!
@brief Turn on or off the floppy motor, if on we wait till we get an
index pulse!
@param motor_on True to turn on motor, False to turn it off
@returns False if turning motor on and no index pulse found, true
otherwise
*/
/**************************************************************************/
virtual bool spin_motor(bool motor_on) = 0;
/**************************************************************************/
/*!
@brief Seek to the desired track, requires the motor to be spun up!
@param track_num The track to step to
@return True If we were able to get to the track location
*/
/**************************************************************************/
virtual bool goto_track(uint8_t track_num) = 0;
/**************************************************************************/
/*!
@brief Which head/side to read from
@param head Head 0 or 1
@return true if the head exists, false otherwise
*/
/**************************************************************************/
virtual bool side(uint8_t head) = 0;
/**************************************************************************/
/*!
@brief The current track location, based on internal caching
@return The cached track location
@note Returns -1 if the track is not known.
*/
/**************************************************************************/
virtual int8_t track(void) = 0;
/**************************************************************************/
/*!
@brief Check whether the floppy in the drive is write protected
@returns False if the floppy is writable, true otherwise
*/
/**************************************************************************/
virtual bool get_write_protect() = 0;
/**************************************************************************/
/*!
@brief Check whether the track0 sensor is active
@returns True if the track0 sensor is active, false otherwise
@note On devices without a track0 sensor, this returns true when
track()==0
*/
/**************************************************************************/
virtual bool get_track0_sense() = 0;
/**************************************************************************/
/*!
@brief Set the density for flux reading and writing
@param high_density false for low density, true for high density
@returns True if the drive interface supports the given density.
*/
/**************************************************************************/
virtual bool set_density(bool high_density) = 0;
uint32_t read_track_mfm(uint8_t *sectors, size_t n_sectors,
uint8_t *sector_validity, bool high_density = true);
uint32_t capture_track(volatile uint8_t *pulses, uint32_t max_pulses,
int32_t *falling_index_offset,
bool store_greaseweazle = false,
uint32_t capture_ms = 0)
__attribute__((optimize("O3")));
bool write_track(uint8_t *pulses, uint32_t num_pulses,
bool store_greaseweazle = false)
__attribute__((optimize("O3")));
void print_pulse_bins(uint8_t *pulses, uint32_t num_pulses,
uint8_t max_bins = 64, bool is_gw_format = false);
void print_pulses(uint8_t *pulses, uint32_t num_pulses,
bool is_gw_format = false);
uint32_t getSampleFrequency(void);
int8_t led_pin = LED_BUILTIN; ///< Debug LED output for tracing
uint16_t select_delay_us = 10; ///< delay after drive select (usecs)
uint16_t step_delay_us = 10000; ///< delay between head steps (usecs)
uint16_t settle_delay_ms = 15; ///< settle delay after seek (msecs)
uint16_t motor_delay_ms = 1000; ///< delay after motor on (msecs)
uint16_t watchdog_delay_ms =
1000; ///< quiescent time until drives reset (msecs)
uint8_t bus_type = BUSTYPE_IBMPC; ///< what kind of floppy drive we're using
Stream *debug_serial = NULL; ///< optional debug stream for serial output
protected:
bool read_index();
private:
#if defined(__SAMD51__)
void deinit_capture(void);
void enable_capture(void);
bool init_generate(void);
void deinit_generate(void);
void enable_generate(void);
void disable_generate(void);
#endif
bool start_polled_capture(void);
void disable_capture(void);
uint16_t sample_flux(bool &new_index_state);
uint16_t sample_flux() {
bool unused;
return sample_flux(unused);
}
bool init_capture(void);
void enable_background_capture(void);
void wait_for_index_pulse_low(void);
int8_t _indexpin, _wrdatapin, _wrgatepin, _rddatapin;
bool _is_apple2;
#ifdef BUSIO_USE_FAST_PINIO
BusIO_PortReg *indexPort;
BusIO_PortMask indexMask;
uint32_t dummyPort = 0;
#endif
};
/**************************************************************************/
/*!
@brief A helper class for chattin with PC & Shugart floppy drives
*/
/**************************************************************************/
class Adafruit_Floppy : public Adafruit_FloppyBase {
public:
Adafruit_Floppy(int8_t densitypin, int8_t indexpin, int8_t selectpin,
int8_t motorpin, int8_t directionpin, int8_t steppin,
int8_t wrdatapin, int8_t wrgatepin, int8_t track0pin,
int8_t protectpin, int8_t rddatapin, int8_t sidepin,
int8_t readypin);
void end() override;
void soft_reset(void) override;
void select(bool selected) override;
bool spin_motor(bool motor_on) override;
bool goto_track(uint8_t track) override;
bool side(uint8_t head) override;
int8_t track(void) override;
void step(bool dir, uint8_t times);
bool set_density(bool high_density) override;
bool get_write_protect() override;
bool get_track0_sense() override;
private:
// theres a lot of GPIO!
int8_t _densitypin, _selectpin, _motorpin, _directionpin, _steppin,
_track0pin, _protectpin, _sidepin, _readypin;
int8_t _track = -1;
};
/**************************************************************************/
/*!
@brief A helper class for chattin with Apple 2 floppy drives
*/
/**************************************************************************/
class Adafruit_Apple2Floppy : public Adafruit_FloppyBase {
public:
/**************************************************************************/
/*!
@brief Constants for use with the step_mode method
*/
/**************************************************************************/
enum StepMode {
STEP_MODE_WHOLE, //< One step moves by one data track
STEP_MODE_HALF, //< Two steps move by one data track
STEP_MODE_QUARTER, //< Four steps move by one data track
};
Adafruit_Apple2Floppy(int8_t indexpin, int8_t selectpin, int8_t phase1pin,
int8_t phase2pin, int8_t phase3pin, int8_t phase4pin,
int8_t wrdatapin, int8_t wrgatepin, int8_t protectpin,
int8_t rddatapin);
void end() override;
void soft_reset(void) override;
void select(bool selected) override;
bool spin_motor(bool motor_on) override;
bool goto_track(uint8_t track) override;
bool side(uint8_t head) override;
int8_t track(void) override;
bool set_density(bool high_density) override;
bool get_write_protect() override;
bool get_track0_sense() override;
int8_t quartertrack();
bool goto_quartertrack(int);
void step_mode(StepMode mode);
private:
int _step_multiplier() const;
// theres not much GPIO!
int8_t _selectpin, _phase1pin, _phase2pin, _phase3pin, _phase4pin,
_protectpin;
int _quartertrack = -1;
StepMode _step_mode = STEP_MODE_HALF;
void _step(int dir, int times);
};
/**************************************************************************/
/*!
This class adds support for the BaseBlockDriver interface to an MFM
encoded floppy disk. This allows it to be used with SdFat's FatFileSystem
class. or for a mass storage device
*/
/**************************************************************************/
class Adafruit_MFM_Floppy : public BaseBlockDriver {
public:
Adafruit_MFM_Floppy(Adafruit_Floppy *floppy,
adafruit_floppy_disk_t format = IBMPC1440K);
bool begin(void);
bool end(void);
uint32_t size(void);
int32_t readTrack(uint8_t track, bool head);
/**! @brief The expected number of sectors per track in this format
@returns The number of sectors per track */
uint8_t sectors_per_track(void) { return _sectors_per_track; }
/**! @brief The expected number of tracks per side in this format
@returns The number of tracks per side */
uint8_t tracks_per_side(void) { return _tracks_per_side; }
//------------- SdFat BaseBlockDRiver API -------------//
virtual bool readBlock(uint32_t block, uint8_t *dst);
virtual bool writeBlock(uint32_t block, const uint8_t *src);
virtual bool syncBlocks();
virtual bool readBlocks(uint32_t block, uint8_t *dst, size_t nb);
virtual bool writeBlocks(uint32_t block, const uint8_t *src, size_t nb);
/**! The raw byte decoded data from the last track read */
uint8_t track_data[MFM_IBMPC1440K_SECTORS_PER_TRACK * MFM_BYTES_PER_SECTOR];
/**! Which tracks from the last track-read were valid MFM/CRC! */
uint8_t track_validity[MFM_IBMPC1440K_SECTORS_PER_TRACK];
private:
#if defined(PICO_BOARD) || defined(__RP2040__) || defined(ARDUINO_ARCH_RP2040)
uint16_t _last;
#endif
uint8_t _sectors_per_track = 0;
uint8_t _tracks_per_side = 0;
int8_t _last_track_read = -1; // last cached track
bool _high_density = true;
Adafruit_Floppy *_floppy = NULL;
adafruit_floppy_disk_t _format = IBMPC1440K;
};
#endif

202
src/Adafruit_MFM_Floppy.cpp Normal file
View file

@ -0,0 +1,202 @@
#include <Adafruit_Floppy.h>
/**************************************************************************/
/*!
@brief Instantiate an MFM-formatted floppy
@param floppy An Adafruit_Floppy object that has the pins defined
@param format What kind of format we will be parsing out - we DO NOT
autodetect!
*/
/**************************************************************************/
Adafruit_MFM_Floppy::Adafruit_MFM_Floppy(Adafruit_Floppy *floppy,
adafruit_floppy_disk_t format) {
_floppy = floppy;
_format = format;
// different formats have different 'hardcoded' sectors and tracks
if (_format == IBMPC1440K) {
_sectors_per_track = MFM_IBMPC1440K_SECTORS_PER_TRACK;
_tracks_per_side = FLOPPY_IBMPC_HD_TRACKS;
_high_density = true;
} else if (_format == IBMPC360K) {
_sectors_per_track = MFM_IBMPC360K_SECTORS_PER_TRACK;
_tracks_per_side = FLOPPY_IBMPC_DD_TRACKS;
_high_density = false;
}
}
/**************************************************************************/
/*!
@brief Initialize and spin up the floppy drive
@returns True if we were able to spin up and detect an index track
*/
/**************************************************************************/
bool Adafruit_MFM_Floppy::begin(void) {
if (!_floppy)
return false;
_floppy->begin();
// now's the time to tweak settings
if (_format == IBMPC360K) {
_floppy->step_delay_us = 65000UL; // lets make it max 65ms not 10ms?
_floppy->settle_delay_ms = 50; // 50ms not 15
}
_floppy->select(true);
return _floppy->spin_motor(true);
}
/**************************************************************************/
/*!
@brief Spin down and deselect the motor and drive
@returns True always
*/
/**************************************************************************/
bool Adafruit_MFM_Floppy::end(void) {
_floppy->spin_motor(false);
_floppy->select(false);
return true;
}
/**************************************************************************/
/*!
@brief Quick calculator for expected max capacity
@returns Size of the drive in bytes
*/
/**************************************************************************/
uint32_t Adafruit_MFM_Floppy::size(void) {
return (uint32_t)_tracks_per_side * FLOPPY_HEADS * _sectors_per_track *
MFM_BYTES_PER_SECTOR;
}
/**************************************************************************/
/*!
@brief Read one track's worth of data and MFM decode it
@param track track number, 0 to whatever is the max tracks for the given
@param head which side to read, false for side 1, true for side 2
format during instantiation (e.g. 40 for DD, 80 for HD)
@returns Number of sectors captured, or -1 if we couldn't seek
*/
/**************************************************************************/
int32_t Adafruit_MFM_Floppy::readTrack(uint8_t track, bool head) {
// Serial.printf("\tSeeking track %d head %d...", track, head);
if (!_floppy->goto_track(track)) {
// Serial.println("failed to seek to track");
return -1;
}
_floppy->side(head);
// Serial.println("done!");
uint32_t captured_sectors = _floppy->read_track_mfm(
track_data, _sectors_per_track, track_validity, _high_density);
/*
Serial.print("Captured %d sectors", captured_sectors);
Serial.print("Validity: ");
for(size_t i=0; i<MFM_SECTORS_PER_TRACK; i++) {
Serial.print(track_validity[i] ? "V" : "?");
}
*/
return captured_sectors;
}
//--------------------------------------------------------------------+
// SdFat BaseBlockDriver API
// A block is 512 bytes
//--------------------------------------------------------------------+
/**************************************************************************/
/*!
@brief Read a 512 byte block of data, may used cached data
@param block Block number, will be split into head and track based on
expected formatting
@param dst Destination buffer
@returns True on success
*/
/**************************************************************************/
bool Adafruit_MFM_Floppy::readBlock(uint32_t block, uint8_t *dst) {
uint8_t track = block / (FLOPPY_HEADS * _sectors_per_track);
uint8_t head = (block / _sectors_per_track) % FLOPPY_HEADS;
uint8_t subsector = block % _sectors_per_track;
// Serial.printf("\tRead request block %d\n", block);
if ((track * FLOPPY_HEADS + head) != _last_track_read) {
// oof it is not cached!
if (readTrack(track, head) == -1) {
return false;
}
_last_track_read = track * FLOPPY_HEADS + head;
}
if (!track_validity[subsector]) {
// Serial.println("subsector invalid");
return false;
}
// Serial.println("OK!");
memcpy(dst, track_data + (subsector * MFM_BYTES_PER_SECTOR),
MFM_BYTES_PER_SECTOR);
return true;
}
/**************************************************************************/
/*!
@brief Read multiple 512 byte block of data, may used cached data
@param block Starting block number, will be split into head and track based
on expected formatting
@param dst Destination buffer
@param nb Number of blocks to read
@returns True on success
*/
/**************************************************************************/
bool Adafruit_MFM_Floppy::readBlocks(uint32_t block, uint8_t *dst, size_t nb) {
// read each block one by one
for (size_t blocknum = 0; blocknum < nb; blocknum++) {
if (!readBlock(block + blocknum, dst + (blocknum * MFM_BYTES_PER_SECTOR)))
return false;
}
return true;
}
/**************************************************************************/
/*!
@brief Write a 512 byte block of data NOT IMPLEMENTED YET
@param block Block number, will be split into head and track based on
expected formatting
@param src Source buffer
@returns True on success, false if failed or unimplemented
*/
/**************************************************************************/
bool Adafruit_MFM_Floppy::writeBlock(uint32_t block, const uint8_t *src) {
Serial.printf("Writing block %d\n", block);
(void *)src;
return false;
}
/**************************************************************************/
/*!
@brief Write multiple 512 byte blocks of data NOT IMPLEMENTED YET
@param block Starting lock number, will be split into head and track based
on expected formatting
@param src Source buffer
@param nb Number of consecutive blocks to write
@returns True on success, false if failed or unimplemented
*/
/**************************************************************************/
bool Adafruit_MFM_Floppy::writeBlocks(uint32_t block, const uint8_t *src,
size_t nb) {
Serial.printf("Writing %d blocks %d\n", nb, block);
(void *)src;
return false;
}
/**************************************************************************/
/*!
@brief Sync written blocks NOT IMPLEMENTED YET
@returns True on success, false if failed or unimplemented
*/
/**************************************************************************/
bool Adafruit_MFM_Floppy::syncBlocks() { return false; }

424
src/arch_rp2.cpp Normal file
View file

@ -0,0 +1,424 @@
#if defined(PICO_BOARD) || defined(__RP2040__) || defined(ARDUINO_ARCH_RP2040)
#include "arch_rp2.h"
#include "greasepack.h"
#include <Arduino.h>
#include <hardware/clocks.h>
#include <hardware/gpio.h>
#include <hardware/pio.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
static const int fluxread_sideset_pin_count = 0;
static const bool fluxread_sideset_enable = 0;
static const uint16_t fluxread[] = {
// ; Count flux pulses and watch for index pin
// ; flux input is the 'jmp pin'. index is "pin zero".
// ; Counts are in units 3 / F_pio, so e.g., at 30MHz 1 count = 0.1us
// ; Count down while waiting for the counter to go HIGH
// ; The only counting is down, so C code will just have to negate the
// count!
// ; Each 'wait one' loop takes 3 instruction-times
// wait_one:
0x0041, // jmp x--, wait_one_next ; acts as a non-conditional decrement
// of x
// wait_one_next:
0x00c3, // jmp pin wait_zero
0x0000, // jmp wait_one
// ; Each 'wait zero' loop takes 3 instruction-times, needing one
// instruction delay
// ; (it has to match the 'wait one' timing exactly)
// wait_zero:
0x0044, // jmp x--, wait_zero_next ; acts as a non-conditional decrement
// of x
// wait_zero_next:
0x01c3, // jmp pin wait_zero [1]
// ; Top bit is index status, bottom 15 bits are inverse of counts
// ; Combined FIFO gives 16 entries (8 32-bit entries) so with the
// ; smallest plausible pulse of 2us there are 250 CPU cycles available
// @125MHz
0x4001, // in pins, 1
0x402f, // in x, 15
// ; Threee cycles for the end of loop, so we need to decrement x to make
// everything
// ; come out right. This has constant timing whether we actually jump back
// vs wrapping.
0x0040, // jmp x--, wait_one
};
static const pio_program_t fluxread_struct = {.instructions = fluxread,
.length = sizeof(fluxread) /
sizeof(fluxread[0]),
.origin = -1};
static const int fluxwrite_sideset_pin_count = 0;
static const bool fluxwrite_sideset_enable = 0;
static const uint16_t fluxwrite[] = {
// loop_flux:
0xe000, // set pins, 0 ; drive pin low
0x6030, // out x, 16 ; get the next timing pulse information, may block
// ;; output the fixed on time. 16 is about 0.67us.
// ;; note that wdc1772 has varying low times, from 570 to 1380us
0xae42, // nop [14]
0xe001, // set pins, 1 ; drive pin high
// loop_high:
0x0044, // jmp x--, loop_high
0x0000, // jmp loop_flux
};
static const pio_program_t fluxwrite_struct = {.instructions = fluxwrite,
.length = sizeof(fluxwrite) /
sizeof(fluxwrite[0]),
.origin = -1};
static const int fluxwrite_apple2_sideset_pin_count = 0;
static const bool fluxwrite_apple2_sideset_enable = 0;
static const uint16_t fluxwrite_apple2[] = {
0x6030, // out x, 16 ; get the next timing pulse information, may block
0xe001, // set pins, 1 ; drive pin high
0xb042, // nop [16]
// loop_high:
0x0043, // jmp x--, loop_high
0x6030, // out x, 16 ; get the next timing pulse information, may block
0xe000, // set pins, 0 ; drive pin low
0xb042, // nop [16]
// loop_low:
0x0047, // jmp x--, loop_low
};
static const pio_program_t fluxwrite_apple2_struct = {
.instructions = fluxwrite_apple2,
.length = sizeof(fluxwrite_apple2) / sizeof(fluxwrite_apple2[0]),
.origin = -1};
typedef struct floppy_singleton {
PIO pio;
const pio_program_t *program;
unsigned sm;
uint16_t offset;
uint16_t half;
} floppy_singleton_t;
static floppy_singleton_t g_reader, g_writer;
const static PIO pio_instances[2] = {pio0, pio1};
static bool allocate_pio_set_program(floppy_singleton_t *info,
const pio_program_t *program) {
memset(info, 0, sizeof(*info));
for (size_t i = 0; i < NUM_PIOS; i++) {
PIO pio = pio_instances[i];
if (!pio_can_add_program(pio, program)) {
continue;
}
int sm = pio_claim_unused_sm(pio, false);
if (sm != -1) {
info->pio = pio;
info->sm = sm;
// cannot fail, we asked nicely already
info->offset = pio_add_program(pio, program);
return true;
}
}
return false;
}
static bool init_capture(int index_pin, int read_pin) {
if (g_reader.pio) {
return true;
}
if (!allocate_pio_set_program(&g_reader, &fluxread_struct)) {
return false;
}
gpio_pull_up(index_pin);
pio_sm_config c = {0, 0, 0};
sm_config_set_wrap(&c, g_reader.offset,
g_reader.offset + fluxread_struct.length - 1);
sm_config_set_jmp_pin(&c, read_pin);
sm_config_set_in_pins(&c, index_pin);
sm_config_set_in_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
pio_sm_set_pins_with_mask(g_reader.pio, g_reader.sm, 1 << read_pin,
1 << read_pin);
float div = (float)clock_get_hz(clk_sys) / (3 * 24e6);
sm_config_set_clkdiv(&c, div); // 72MHz capture clock / 24MHz sample rate
pio_sm_init(g_reader.pio, g_reader.sm, g_reader.offset, &c);
return true;
}
static void start_common() {
pio_sm_exec(g_reader.pio, g_reader.sm, g_reader.offset);
pio_sm_restart(g_reader.pio, g_reader.sm);
}
static bool data_available() {
return g_reader.half || !pio_sm_is_rx_fifo_empty(g_reader.pio, g_reader.sm);
}
static uint16_t read_fifo() {
if (g_reader.half) {
uint16_t result = g_reader.half;
g_reader.half = 0;
return result;
}
uint32_t value = pio_sm_get_blocking(g_reader.pio, g_reader.sm);
g_reader.half = value >> 16;
return value & 0xffff;
}
static void disable_capture(void) {
pio_sm_set_enabled(g_reader.pio, g_reader.sm, false);
}
static void free_capture(void) {
if (!g_reader.pio) {
// already deinit
return;
}
disable_capture();
pio_sm_unclaim(g_reader.pio, g_reader.sm);
pio_remove_program(g_reader.pio, &fluxread_struct, g_reader.offset);
memset(&g_reader, 0, sizeof(g_reader));
}
static uint8_t *capture_foreground(int index_pin, uint8_t *start, uint8_t *end,
int32_t *falling_index_offset,
bool store_greaseweazle,
uint32_t capture_counts) {
uint8_t *ptr = start;
if (falling_index_offset) {
*falling_index_offset = -1;
}
start_common();
// wait for a falling edge of index pin, then enable the capture peripheral
while (!gpio_get(index_pin)) { /* NOTHING */
}
while (gpio_get(index_pin)) { /* NOTHING */
}
uint32_t total_counts = 0;
noInterrupts();
pio_sm_clear_fifos(g_reader.pio, g_reader.sm);
pio_sm_set_enabled(g_reader.pio, g_reader.sm, true);
int last = read_fifo();
bool last_index = gpio_get(index_pin);
while (ptr != end) {
/* Handle index */
bool now_index = gpio_get(index_pin);
if (!now_index && last_index) {
if (falling_index_offset) {
*falling_index_offset = ptr - start;
if (!capture_counts) {
break;
}
}
}
last_index = now_index;
if (!data_available) {
continue;
}
int data = read_fifo();
int delta = last - data;
if (delta < 0)
delta += 65536;
delta /= 2;
last = data;
total_counts += delta;
if (store_greaseweazle) {
ptr = greasepack(ptr, end, delta);
} else {
*ptr++ = delta > 255 ? 255 : delta;
}
if (capture_counts != 0 && total_counts >= capture_counts) {
break;
}
}
interrupts();
disable_capture();
return ptr;
}
static void enable_capture_fifo() { start_common(); }
static bool init_write(int wrdata_pin, bool is_apple2) {
if (g_writer.pio) {
return true;
}
const pio_program_t *program =
is_apple2 ? &fluxwrite_apple2_struct : &fluxwrite_struct;
g_writer.program = program;
if (!allocate_pio_set_program(&g_writer, program)) {
return false;
}
uint32_t wrdata_bit = 1u << wrdata_pin;
pio_gpio_init(g_writer.pio, wrdata_pin);
pio_sm_set_pindirs_with_mask(g_writer.pio, g_writer.sm, wrdata_bit,
wrdata_bit);
pio_sm_set_pins_with_mask(g_writer.pio, g_writer.sm, wrdata_bit, wrdata_bit);
pio_sm_set_pins_with_mask(g_writer.pio, g_writer.sm, 0, wrdata_bit);
pio_sm_set_pins_with_mask(g_writer.pio, g_writer.sm, wrdata_bit, wrdata_bit);
pio_sm_config c{};
sm_config_set_wrap(&c, g_writer.offset,
g_writer.offset + program->length - 1);
sm_config_set_set_pins(&c, wrdata_pin, 1);
sm_config_set_out_shift(&c, true, true, 16);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
float div = (float)clock_get_hz(clk_sys) / (24e6);
sm_config_set_clkdiv(&c, div); // 24MHz output clock
pio_sm_init(g_writer.pio, g_writer.sm, g_writer.offset, &c);
return true;
}
static void enable_write() {}
#define OVERHEAD (20) // minimum pulse length due to PIO overhead, about 0.833us
static void write_fifo(unsigned value) {
if (value < OVERHEAD) {
value = 1;
} else {
value -= OVERHEAD;
if (value > 0xffff)
value = 0xffff;
}
pio_sm_put_blocking(g_writer.pio, g_writer.sm, value);
}
static void disable_write() {
pio_sm_set_enabled(g_writer.pio, g_writer.sm, false);
}
static void write_foreground(int index_pin, int wrgate_pin, uint8_t *pulses,
uint8_t *pulse_end, bool store_greaseweazle) {
// don't start during an index pulse
while (!gpio_get(index_pin)) { /* NOTHING */
}
// wait for falling edge of index pin
while (gpio_get(index_pin)) { /* NOTHING */
}
pinMode(wrgate_pin, OUTPUT);
digitalWrite(wrgate_pin, LOW);
noInterrupts();
pio_sm_set_enabled(g_writer.pio, g_writer.sm, false);
pio_sm_clear_fifos(g_writer.pio, g_writer.sm);
pio_sm_exec(g_writer.pio, g_writer.sm, g_writer.offset);
while (!pio_sm_is_tx_fifo_full(g_writer.pio, g_writer.sm)) {
unsigned value = greaseunpack(&pulses, pulse_end, store_greaseweazle);
value = (value < OVERHEAD) ? 1 : value - OVERHEAD;
pio_sm_put_blocking(g_writer.pio, g_writer.sm, value);
}
pio_sm_set_enabled(g_writer.pio, g_writer.sm, true);
bool old_index_state = false;
int i = 0;
while (pulses != pulse_end) {
bool index_state = gpio_get(index_pin);
if (old_index_state && !index_state) {
// falling edge of index pin
break;
}
while (!pio_sm_is_tx_fifo_full(g_writer.pio, g_writer.sm)) {
unsigned value = greaseunpack(&pulses, pulse_end, store_greaseweazle);
value = (value < OVERHEAD) ? 1 : value - OVERHEAD;
pio_sm_put_blocking(g_writer.pio, g_writer.sm, value);
}
old_index_state = index_state;
}
interrupts();
pio_sm_set_enabled(g_writer.pio, g_writer.sm, false);
pinMode(wrgate_pin, INPUT_PULLUP);
}
static void free_write() {
if (!g_writer.pio) {
// already deinit
return;
}
disable_write();
pio_sm_unclaim(g_writer.pio, g_writer.sm);
pio_remove_program(g_writer.pio, g_writer.program, g_writer.offset);
memset(&g_writer, 0, sizeof(g_writer));
}
#ifdef __cplusplus
#include <Adafruit_Floppy.h>
uint32_t rp2040_flux_capture(int index_pin, int rdpin, volatile uint8_t *pulses,
volatile uint8_t *pulse_end,
int32_t *falling_index_offset,
bool store_greaseweazle, uint32_t capture_counts) {
if (!init_capture(index_pin, rdpin)) {
return 0;
}
uint32_t result =
capture_foreground(index_pin, (uint8_t *)pulses, (uint8_t *)pulse_end,
falling_index_offset, store_greaseweazle,
capture_counts) -
pulses;
free_capture();
return result;
}
unsigned _last = ~0u;
bool Adafruit_FloppyBase::init_capture(void) {
_last = ~0u;
return ::init_capture(_indexpin, _rddatapin);
}
bool Adafruit_FloppyBase::start_polled_capture(void) {
if (!init_capture())
return false;
start_common();
pio_sm_set_enabled(g_reader.pio, g_reader.sm, true);
return true;
}
void Adafruit_FloppyBase::disable_capture(void) { ::disable_capture(); }
uint16_t mfm_io_sample_flux(bool *index) {
if (_last == ~0u) {
_last = read_fifo();
}
int data = read_fifo();
int delta = _last - data;
_last = data;
if (delta < 0)
delta += 65536;
*index = data & 1;
return delta / 2;
}
bool rp2040_flux_write(int index_pin, int wrgate_pin, int wrdata_pin,
uint8_t *pulses, uint8_t *pulse_end,
bool store_greaseweazle, bool is_apple2) {
if (!init_write(wrdata_pin, is_apple2)) {
return false;
}
write_foreground(index_pin, wrgate_pin, (uint8_t *)pulses,
(uint8_t *)pulse_end, store_greaseweazle);
free_write();
return true;
}
#endif
#endif

17
src/arch_rp2.h Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#if defined(ARDUINO_ARCH_RP2040)
#define read_index_fast() gpio_get(_indexpin)
#define read_data() gpio_get(_rddatapin)
#define set_debug_led() gpio_put(led_pin, 1)
#define clr_debug_led() gpio_put(led_pin, 0)
#define set_write() gpio_put(_wrdatapin, 1)
#define clr_write() gpio_put(_wrdatapin, 0)
#include <stdint.h>
extern uint32_t
rp2040_flux_capture(int indexpin, int rdpin, volatile uint8_t *pulses,
volatile uint8_t *end, int32_t *falling_index_offset,
bool store_greaseweazle, uint32_t capture_counts);
extern bool rp2040_flux_write(int index_pin, int wrgate_pin, int wrdata_pin,
uint8_t *pulses, uint8_t *pulse_end,
bool store_greaseweazel, bool is_apple2);
#endif

440
src/arch_samd51.cpp Normal file
View file

@ -0,0 +1,440 @@
#if defined(__SAMD51__)
#include <Adafruit_Floppy.h>
#include <wiring_private.h> // pinPeripheral() func
static const struct {
Tc *tc; // -> Timer/Counter base address
IRQn_Type IRQn; // Interrupt number
int gclk; // GCLK ID
int evu; // EVSYS user ID
} tcList[] = {{TC0, TC0_IRQn, TC0_GCLK_ID, EVSYS_ID_USER_TC0_EVU},
{TC1, TC1_IRQn, TC1_GCLK_ID, EVSYS_ID_USER_TC1_EVU},
{TC2, TC2_IRQn, TC2_GCLK_ID, EVSYS_ID_USER_TC2_EVU},
{TC3, TC3_IRQn, TC3_GCLK_ID, EVSYS_ID_USER_TC3_EVU},
#ifdef TC4
{TC4, TC4_IRQn, TC4_GCLK_ID, EVSYS_ID_USER_TC4_EVU},
#endif
#ifdef TC5
{TC5, TC5_IRQn, TC5_GCLK_ID, EVSYS_ID_USER_TC5_EVU},
#endif
#ifdef TC6
{TC6, TC6_IRQn, TC6_GCLK_ID, EVSYS_ID_USER_TC6_EVU},
#endif
#ifdef TC7
{TC7, TC7_IRQn, TC7_GCLK_ID, EVSYS_ID_USER_TC7_EVU}
#endif
};
Tc *theReadTimer = NULL;
Tc *theWriteTimer = NULL;
int g_cap_tc_num;
volatile uint8_t *g_flux_pulses = NULL;
volatile uint32_t g_max_pulses = 0;
volatile uint32_t g_num_pulses = 0;
volatile bool g_store_greaseweazle = false;
volatile uint8_t g_timing_div = 2;
volatile bool g_writing_pulses = false;
void FLOPPY_TC_HANDLER() // Interrupt Service Routine (ISR) for timer TCx
{
// Check for match counter 0 (MC0) interrupt
if (theReadTimer && theReadTimer->COUNT16.INTFLAG.bit.MC0) {
uint16_t ticks =
theReadTimer->COUNT16.CC[0].reg / g_timing_div; // Copy the period
if (ticks == 0) {
// dont do something if its 0 - thats wierd!
} else if (ticks < 250 || !g_store_greaseweazle) {
// 1-249: One byte.
g_flux_pulses[g_num_pulses++] = min(249, ticks);
} else {
uint8_t high = (ticks - 250) / 255;
if (high < 5) {
// 250-1524: Two bytes.
g_flux_pulses[g_num_pulses++] = 250 + high;
g_flux_pulses[g_num_pulses++] = 1 + ((ticks - 250) % 255);
} else {
// TODO MEME FIX!
/* 1525-(2^28-1): Seven bytes.
g_flux_pulses[g_num_pulses++] = 0xff;
g_flux_pulses[g_num_pulses++] = FLUXOP_SPACE;
_write_28bit(ticks - 249);
u_buf[U_MASK(u_prod++)] = 249;
}
*/
}
}
}
// Check for match counter 1 (MC1) interrupt
if (theReadTimer && theReadTimer->COUNT16.INTFLAG.bit.MC1) {
uint16_t pulsewidth =
theReadTimer->COUNT16.CC[1].reg; // Copy the pulse width, DONT REMOVE
(void)pulsewidth;
}
if (theWriteTimer && theWriteTimer->COUNT16.INTFLAG.bit.MC0) {
// Because this uses CCBUF registers (updating CC on next timer rollover),
// the pulse_index check here looks odd, checking both under AND over
// num_pulses This is normal and OK and intended, because of the
// last-pulse-out case where we need one extra invocation of the interrupt
// to allow that pulse out before resetting PWM to steady high and disabling
// the interrupt.
if (g_num_pulses < g_max_pulses) {
// Set period for next pulse
uint16_t ticks = g_flux_pulses[g_num_pulses];
if (ticks == 0) {
// dont do something if its 0 - thats wierd!
} else if (ticks < 250 || !g_store_greaseweazle) {
// 1-249: One byte.
ticks = min(249, ticks);
} else {
// 250-1524: Two bytes.
uint16_t high = ((ticks - 250) + 1) * 255;
g_num_pulses++;
ticks = high + g_flux_pulses[g_num_pulses];
}
theWriteTimer->COUNT16.CCBUF[0].reg = ticks;
} else if (g_num_pulses > g_max_pulses) {
// Last pulse out was allowed its one extra PWM cycle, done now
theWriteTimer->COUNT16.CCBUF[1].reg = 0; // Steady high on next pulse
theWriteTimer->COUNT16.INTENCLR.bit.MC0 = 1; // Disable interrupt
g_writing_pulses = false;
}
g_num_pulses++; // Outside if/else to allow last-pulse case
theWriteTimer->COUNT16.INTFLAG.bit.MC0 = 1; // Clear interrupt flag
}
}
// this isnt great but how else can we dynamically choose the pin? :/
void TC0_Handler() { FLOPPY_TC_HANDLER(); }
void TC1_Handler() { FLOPPY_TC_HANDLER(); }
void TC2_Handler() { FLOPPY_TC_HANDLER(); }
void TC3_Handler() { FLOPPY_TC_HANDLER(); }
void TC4_Handler() { FLOPPY_TC_HANDLER(); }
/************************************************************************/
static bool init_capture_timer(int _rddatapin, Stream *debug_serial) {
MCLK->APBBMASK.reg |=
MCLK_APBBMASK_EVSYS; // Switch on the event system peripheral
// Enable the port multiplexer on READDATA
PinDescription pinDesc = g_APinDescription[_rddatapin];
uint32_t capture_port = pinDesc.ulPort;
uint32_t capture_pin = pinDesc.ulPin;
EExt_Interrupts capture_irq = pinDesc.ulExtInt;
if (capture_irq == NOT_AN_INTERRUPT) {
if (debug_serial)
debug_serial->println("Not an interrupt pin!");
return false;
}
uint32_t tcNum = GetTCNumber(pinDesc.ulPWMChannel);
uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);
if (tcNum < TCC_INST_NUM) {
if (pinDesc.ulTCChannel != NOT_ON_TIMER) {
if (debug_serial)
debug_serial->println(
"PWM is on a TCC not TC, lets look at the TCChannel");
tcNum = GetTCNumber(pinDesc.ulTCChannel);
tcChannel = GetTCChannelNumber(pinDesc.ulTCChannel);
}
if (tcNum < TCC_INST_NUM) {
if (debug_serial)
debug_serial->println("Couldn't find a TC channel for this pin :(");
return false;
}
}
g_cap_tc_num = tcNum -= TCC_INST_NUM; // adjust naming
if (debug_serial)
debug_serial->printf("readdata on port %d and pin %d, IRQ #%d, TC%d.%d\n\r",
capture_port, capture_pin, capture_irq, tcNum,
tcChannel);
theReadTimer = tcList[tcNum].tc;
if (debug_serial)
debug_serial->printf("TC GCLK ID=%d, EVU=%d\n\r", tcList[tcNum].gclk,
tcList[tcNum].evu);
// Setup INPUT capture clock
GCLK->PCHCTRL[tcList[tcNum].gclk].reg =
GCLK_PCHCTRL_GEN_GCLK1_Val |
(1 << GCLK_PCHCTRL_CHEN_Pos); // use GCLK1 to get 48MHz on SAMD51
PORT->Group[capture_port].PINCFG[capture_pin].bit.PMUXEN = 1;
// Set-up the pin as an EIC (interrupt) peripheral on READDATA
if (capture_pin % 2 == 0) { // even pmux
PORT->Group[capture_port].PMUX[capture_pin >> 1].reg |= PORT_PMUX_PMUXE(0);
} else {
PORT->Group[capture_port].PMUX[capture_pin >> 1].reg |= PORT_PMUX_PMUXO(0);
}
EIC->CTRLA.bit.ENABLE = 0; // Disable the EIC peripheral
while (EIC->SYNCBUSY.bit.ENABLE)
; // Wait for synchronization
// Look for right CONFIG register to be addressed
uint8_t eic_config, eic_config_pos;
if (capture_irq > EXTERNAL_INT_7) {
eic_config = 1;
eic_config_pos = (capture_irq - 8) << 2;
} else {
eic_config = 0;
eic_config_pos = capture_irq << 2;
}
// Set event on detecting a HIGH level
EIC->CONFIG[eic_config].reg &= ~(EIC_CONFIG_SENSE0_Msk << eic_config_pos);
EIC->CONFIG[eic_config].reg |= EIC_CONFIG_SENSE0_HIGH_Val << eic_config_pos;
EIC->EVCTRL.reg =
1 << capture_irq; // Enable event output on external interrupt
EIC->INTENCLR.reg = 1 << capture_irq; // Clear interrupt on external interrupt
EIC->ASYNCH.reg = 1 << capture_irq; // Set-up interrupt as asynchronous input
EIC->CTRLA.bit.ENABLE = 1; // Enable the EIC peripheral
while (EIC->SYNCBUSY.bit.ENABLE)
; // Wait for synchronization
// Select the event system user on channel 0 (USER number = channel number +
// 1)
EVSYS->USER[tcList[tcNum].evu].reg =
EVSYS_USER_CHANNEL(1); // Set the event user (receiver) as timer
// Select the event system generator on channel 0
EVSYS->Channel[0].CHANNEL.reg =
EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(
EVSYS_ID_GEN_EIC_EXTINT_0 +
capture_irq); // Set event generator (sender) as ext int
theReadTimer->COUNT16.EVCTRL.reg =
TC_EVCTRL_TCEI | // Enable the TCC event input
// TC_EVCTRL_TCINV | // Invert the event
// input
TC_EVCTRL_EVACT_PPW; // Set up the timer for capture: CC0 period, CC1
// pulsewidth
NVIC_SetPriority(tcList[tcNum].IRQn,
0); // Set the Nested Vector Interrupt Controller (NVIC)
// priority for TCx to 0 (highest)
theReadTimer->COUNT16.INTENSET.reg =
TC_INTENSET_MC1 | // Enable compare channel 1 (CC1) interrupts
TC_INTENSET_MC0; // Enable compare channel 0 (CC0) interrupts
theReadTimer->COUNT16.CTRLA.reg =
TC_CTRLA_CAPTEN1 | // Enable pulse capture on CC1
TC_CTRLA_CAPTEN0 | // Enable pulse capture on CC0
// TC_CTRLA_PRESCSYNC_PRESC | // Roll over on prescaler clock
// TC_CTRLA_PRESCALER_DIV1 | // Set the prescaler
TC_CTRLA_MODE_COUNT16; // Set the timer to 16-bit mode
return true;
}
static void enable_capture_timer(bool interrupt_driven) {
if (!theReadTimer)
return;
if (interrupt_driven) {
NVIC_EnableIRQ(
tcList[g_cap_tc_num].IRQn); // Connect the TCx timer to the Nested
// Vector Interrupt Controller (NVIC)
} else {
NVIC_DisableIRQ(
tcList[g_cap_tc_num].IRQn); // Disconnect the TCx timer from the Nested
// Vector Interrupt Controller (NVIC)
}
theReadTimer->COUNT16.CTRLA.bit.ENABLE = 1; // Enable the TC timer
while (theReadTimer->COUNT16.SYNCBUSY.bit.ENABLE)
; // Wait for synchronization
}
static bool init_generate_timer(int _wrdatapin, Stream *debug_serial) {
MCLK->APBBMASK.reg |=
MCLK_APBBMASK_EVSYS; // Switch on the event system peripheral
// Enable the port multiplexer on WRITEDATA
PinDescription pinDesc = g_APinDescription[_wrdatapin];
uint32_t generate_port = pinDesc.ulPort;
uint32_t generate_pin = pinDesc.ulPin;
uint32_t tcNum = GetTCNumber(pinDesc.ulPWMChannel);
uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);
if (tcNum < TCC_INST_NUM) {
// OK so we're a TC!
if (pinDesc.ulTCChannel != NOT_ON_TIMER) {
if (debug_serial)
debug_serial->println("PWM is on a TC");
tcNum = GetTCNumber(pinDesc.ulTCChannel);
tcChannel = GetTCChannelNumber(pinDesc.ulTCChannel);
pinPeripheral(_wrdatapin, PIO_TIMER); // PIO_TIMER if using a TC periph
}
if (tcNum < TCC_INST_NUM) {
if (debug_serial)
debug_serial->println("Couldn't find a TC channel for this pin :(");
return false;
}
} else {
pinPeripheral(_wrdatapin,
PIO_TIMER_ALT); // PIO_TIMER_ALT if using a TCC periph
}
tcNum -= TCC_INST_NUM; // adjust naming
if (debug_serial)
debug_serial->printf("writedata on port %d and pin %d,, TC%d.%d\n\r",
generate_port, generate_pin, tcNum, tcChannel);
// Because of the PWM mode used, we MUST use a TC#/WO[1] pin, can't
// use WO[0]. Different timers would need different pins,
// but the WO[1] thing is NOT negotiable.
if (tcChannel != 1) {
debug_serial->println("Must be on TCx.1, but we're not!");
return false;
}
theWriteTimer = tcList[tcNum].tc;
if (debug_serial)
debug_serial->printf("TC GCLK ID=%d, EVU=%d\n\r", tcList[tcNum].gclk,
tcList[tcNum].evu);
// Configure TC timer source as GCLK1 (48 MHz peripheral clock)
GCLK->PCHCTRL[tcList[tcNum].gclk].bit.CHEN = 0;
while (GCLK->PCHCTRL[tcList[tcNum].gclk].bit.CHEN)
; // Wait for disable
GCLK_PCHCTRL_Type pchctrl;
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK1_Val; // GCLK1 is 48 MHz
pchctrl.bit.CHEN = 1;
GCLK->PCHCTRL[tcList[tcNum].gclk].reg = pchctrl.reg;
while (!GCLK->PCHCTRL[tcList[tcNum].gclk].bit.CHEN)
; // Wait for enable
// Software reset timer/counter to default state (also disables it)
theWriteTimer->COUNT16.CTRLA.bit.SWRST = 1;
while (theWriteTimer->COUNT16.SYNCBUSY.bit.SWRST)
;
// Configure for MPWM, 1:2 prescale (24 MHz). 16-bit mode is defailt.
theWriteTimer->COUNT16.WAVE.bit.WAVEGEN = 3; // Match PWM mode
theWriteTimer->COUNT16.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_DIV2_Val;
// MPWM mode is weird but necessary because Normal PWM has a fixed TOP value.
// Count-up, no one-shot is default state, no need to fiddle those bits.
// Invert PWM channel 1 so it starts low, goes high on match
theWriteTimer->COUNT16.DRVCTRL.bit.INVEN1 = 1; // INVEN1 = channel 1
// Enable timer
theWriteTimer->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;
while (theWriteTimer->COUNT16.SYNCBUSY.bit.ENABLE)
;
// IRQ is enabled but match-compare interrupt isn't enabled
// until send_pulses() is called.
NVIC_DisableIRQ(tcList[tcNum].IRQn);
NVIC_ClearPendingIRQ(tcList[tcNum].IRQn);
NVIC_SetPriority(tcList[tcNum].IRQn, 0); // Top priority
NVIC_EnableIRQ(tcList[tcNum].IRQn);
return true;
}
static void enable_generate_timer(void) {
theWriteTimer->COUNT16.COUNT.reg =
0; // Reset counter so we can time this right
while (theWriteTimer->COUNT16.SYNCBUSY.bit.COUNT)
;
// Trigger 1st pulse in ~1/4 uS (need moment to set up other registers)
theWriteTimer->COUNT16.CC[0].reg = 5;
while (theWriteTimer->COUNT16.SYNCBUSY.bit.CC0)
;
// Set up duration of first pulse when COUNT rolls over
theWriteTimer->COUNT16.CCBUF[0].reg = g_flux_pulses[0];
while (theWriteTimer->COUNT16.SYNCBUSY.bit.CC0)
;
// Set up LOW period of pulses when COUNT rolls over
theWriteTimer->COUNT16.CCBUF[1].reg = 5; // 0.25 uS low pulses
while (theWriteTimer->COUNT16.SYNCBUSY.bit.CC1)
;
// Enable match compare channel 0 interrupt
theWriteTimer->COUNT16.INTENSET.bit.MC0 = 1;
}
#ifdef __cplusplus
bool Adafruit_FloppyBase::init_capture(void) {
return init_capture_timer(_rddatapin, debug_serial);
}
void Adafruit_FloppyBase::deinit_capture(void) {
if (!theReadTimer)
return;
// Software reset timer/counter to default state (also disables it)
theReadTimer->COUNT16.CTRLA.bit.SWRST = 1;
while (theReadTimer->COUNT16.SYNCBUSY.bit.SWRST)
;
theReadTimer = NULL;
}
void Adafruit_FloppyBase::enable_capture(void) { enable_capture_timer(true); }
void Adafruit_FloppyBase::disable_capture(void) {
if (!theReadTimer)
return;
theReadTimer->COUNT16.CTRLA.bit.ENABLE = 0; // disable the TC timer
}
bool Adafruit_FloppyBase::init_generate(void) {
return init_generate_timer(_wrdatapin, debug_serial);
}
void Adafruit_FloppyBase::deinit_generate(void) {
if (!theWriteTimer)
return;
// Software reset timer/counter to default state (also disables it)
theWriteTimer->COUNT16.CTRLA.bit.SWRST = 1;
while (theWriteTimer->COUNT16.SYNCBUSY.bit.SWRST)
;
theWriteTimer = NULL;
}
void Adafruit_FloppyBase::enable_generate(void) { enable_generate_timer(); }
void Adafruit_FloppyBase::disable_generate(void) {
if (!theWriteTimer)
return;
theWriteTimer->COUNT16.CTRLA.bit.ENABLE = 0; // disable the TC timer
}
bool Adafruit_FloppyBase::start_polled_capture(void) {
::enable_capture_timer(false);
return true;
}
uint16_t mfm_io_sample_flux(bool *index) {
if (!theReadTimer)
return ~(uint16_t)0;
// Check for match counter 0 (MC0) interrupt
while (!(theReadTimer->COUNT16.INTFLAG.bit.MC0)) {
/* NOTHING */
}
uint16_t ticks =
theReadTimer->COUNT16.CC[0].reg / g_timing_div; // Copy the period
return ticks;
}
#endif
#endif

101
src/greasepack.h Normal file
View 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

266
src/mfm_impl.h Normal file
View file

@ -0,0 +1,266 @@
// SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries
//
// SPDX-License-Identifier: MIT
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#pragma GCC push_options
#pragma GCC optimize("-O3")
typedef struct mfm_io mfm_io_t;
#ifndef MFM_IO_MMIO
#define MFM_IO_MMIO (0)
#endif
// If you have a memory mapped peripheral, define MFM_IO_MMIO to get an
// implementation of the mfm_io functions. then, just populate the fields with
// the actual registers to use and define T2_5 and T3_5 to the empirical values
// dividing between T2/3 and T3/4 pulses.
#if MFM_IO_MMIO
struct mfm_io {
const volatile uint32_t *index_port;
uint32_t index_mask;
const volatile uint32_t *data_port;
uint32_t data_mask;
unsigned index_state;
unsigned index_count;
};
#endif
typedef enum { pulse_10, pulse_100, pulse_1000 } mfm_io_symbol_t;
typedef enum { odd = 0, even = 1 } mfm_state_t;
enum { IDAM = 0xfe, DAM = 0xfb };
enum { blocksize = 512, overhead = 3, metadata_size = 7 };
__attribute__((always_inline)) static inline mfm_io_symbol_t
mfm_io_read_symbol(mfm_io_t *io);
static void mfm_io_reset_sync_count(mfm_io_t *io);
__attribute__((always_inline)) static int mfm_io_get_sync_count(mfm_io_t *io);
// Automatically generated CRC function
// polynomial: 0x11021
static uint16_t crc16(uint8_t *data, int len, uint16_t crc) {
static const uint16_t table[256] = {
0x0000U, 0x1021U, 0x2042U, 0x3063U, 0x4084U, 0x50A5U, 0x60C6U, 0x70E7U,
0x8108U, 0x9129U, 0xA14AU, 0xB16BU, 0xC18CU, 0xD1ADU, 0xE1CEU, 0xF1EFU,
0x1231U, 0x0210U, 0x3273U, 0x2252U, 0x52B5U, 0x4294U, 0x72F7U, 0x62D6U,
0x9339U, 0x8318U, 0xB37BU, 0xA35AU, 0xD3BDU, 0xC39CU, 0xF3FFU, 0xE3DEU,
0x2462U, 0x3443U, 0x0420U, 0x1401U, 0x64E6U, 0x74C7U, 0x44A4U, 0x5485U,
0xA56AU, 0xB54BU, 0x8528U, 0x9509U, 0xE5EEU, 0xF5CFU, 0xC5ACU, 0xD58DU,
0x3653U, 0x2672U, 0x1611U, 0x0630U, 0x76D7U, 0x66F6U, 0x5695U, 0x46B4U,
0xB75BU, 0xA77AU, 0x9719U, 0x8738U, 0xF7DFU, 0xE7FEU, 0xD79DU, 0xC7BCU,
0x48C4U, 0x58E5U, 0x6886U, 0x78A7U, 0x0840U, 0x1861U, 0x2802U, 0x3823U,
0xC9CCU, 0xD9EDU, 0xE98EU, 0xF9AFU, 0x8948U, 0x9969U, 0xA90AU, 0xB92BU,
0x5AF5U, 0x4AD4U, 0x7AB7U, 0x6A96U, 0x1A71U, 0x0A50U, 0x3A33U, 0x2A12U,
0xDBFDU, 0xCBDCU, 0xFBBFU, 0xEB9EU, 0x9B79U, 0x8B58U, 0xBB3BU, 0xAB1AU,
0x6CA6U, 0x7C87U, 0x4CE4U, 0x5CC5U, 0x2C22U, 0x3C03U, 0x0C60U, 0x1C41U,
0xEDAEU, 0xFD8FU, 0xCDECU, 0xDDCDU, 0xAD2AU, 0xBD0BU, 0x8D68U, 0x9D49U,
0x7E97U, 0x6EB6U, 0x5ED5U, 0x4EF4U, 0x3E13U, 0x2E32U, 0x1E51U, 0x0E70U,
0xFF9FU, 0xEFBEU, 0xDFDDU, 0xCFFCU, 0xBF1BU, 0xAF3AU, 0x9F59U, 0x8F78U,
0x9188U, 0x81A9U, 0xB1CAU, 0xA1EBU, 0xD10CU, 0xC12DU, 0xF14EU, 0xE16FU,
0x1080U, 0x00A1U, 0x30C2U, 0x20E3U, 0x5004U, 0x4025U, 0x7046U, 0x6067U,
0x83B9U, 0x9398U, 0xA3FBU, 0xB3DAU, 0xC33DU, 0xD31CU, 0xE37FU, 0xF35EU,
0x02B1U, 0x1290U, 0x22F3U, 0x32D2U, 0x4235U, 0x5214U, 0x6277U, 0x7256U,
0xB5EAU, 0xA5CBU, 0x95A8U, 0x8589U, 0xF56EU, 0xE54FU, 0xD52CU, 0xC50DU,
0x34E2U, 0x24C3U, 0x14A0U, 0x0481U, 0x7466U, 0x6447U, 0x5424U, 0x4405U,
0xA7DBU, 0xB7FAU, 0x8799U, 0x97B8U, 0xE75FU, 0xF77EU, 0xC71DU, 0xD73CU,
0x26D3U, 0x36F2U, 0x0691U, 0x16B0U, 0x6657U, 0x7676U, 0x4615U, 0x5634U,
0xD94CU, 0xC96DU, 0xF90EU, 0xE92FU, 0x99C8U, 0x89E9U, 0xB98AU, 0xA9ABU,
0x5844U, 0x4865U, 0x7806U, 0x6827U, 0x18C0U, 0x08E1U, 0x3882U, 0x28A3U,
0xCB7DU, 0xDB5CU, 0xEB3FU, 0xFB1EU, 0x8BF9U, 0x9BD8U, 0xABBBU, 0xBB9AU,
0x4A75U, 0x5A54U, 0x6A37U, 0x7A16U, 0x0AF1U, 0x1AD0U, 0x2AB3U, 0x3A92U,
0xFD2EU, 0xED0FU, 0xDD6CU, 0xCD4DU, 0xBDAAU, 0xAD8BU, 0x9DE8U, 0x8DC9U,
0x7C26U, 0x6C07U, 0x5C64U, 0x4C45U, 0x3CA2U, 0x2C83U, 0x1CE0U, 0x0CC1U,
0xEF1FU, 0xFF3EU, 0xCF5DU, 0xDF7CU, 0xAF9BU, 0xBFBAU, 0x8FD9U, 0x9FF8U,
0x6E17U, 0x7E36U, 0x4E55U, 0x5E74U, 0x2E93U, 0x3EB2U, 0x0ED1U, 0x1EF0U,
};
while (len > 0) {
crc = table[*data ^ (uint8_t)(crc >> 8)] ^ (crc << 8);
data++;
len--;
}
return crc;
}
enum { triple_mark_magic = 0x09926499, triple_mark_mask = 0x0fffffff };
__attribute__((always_inline)) inline static bool
wait_triple_sync_mark(mfm_io_t *io) {
uint32_t state = 0;
while (mfm_io_get_sync_count(io) < 3 && state != triple_mark_magic) {
state = ((state << 2) | mfm_io_read_symbol(io)) & triple_mark_mask;
}
return state == triple_mark_magic;
}
// Compute the MFM CRC of the data, _assuming it was preceded by three 0xa1 sync
// bytes
static int crc16_preloaded(unsigned char *buf, size_t n) {
return crc16((uint8_t *)buf, n, 0xcdb4);
}
// Copy 'n' bytes of data into 'buf'
__attribute__((always_inline)) inline static void
receive(mfm_io_t *io, unsigned char *buf, size_t n) {
// `tmp` holds up to 9 bits of data, in bits 6..15.
unsigned tmp = 0, weight = 0x8000;
#define PUT_BIT(x) \
do { \
if (x) \
tmp |= weight; \
weight >>= 1; \
} while (0)
// In MFM, flux marks can be 2, 3, or 4 "T" apart. These three signals
// stand for the bit sequences 10, 100, and 1000. However, half of the
// bits are data bits, and half are 'clock' bits. We have to keep track of
// whether [in the next symbol] we want the "even" bit(s) or the "odd" bit(s):
//
// 10 - leaves even/odd (parity) unchanged
// 100 - inverts even/odd (parity)
// 1000 - leaves even/odd (parity) unchanged
// ^ ^ data bits if state is even
// ^ ^ data bits if state is odd
// We do this by knowing that when we arrive, we are waiting to parse the
// final '1' data bit of the MFM sync mark. This means we apply a special rule
// to the first word, starting as though in the 'even' state but not recording
// the '1' bit.
mfm_io_symbol_t s = mfm_io_read_symbol(io);
mfm_state_t state = even;
switch (s) {
case pulse_100: // first data bit is a 0, and we start in the ODD state
state = odd;
/* fallthrough */
case pulse_1000: // first data bit is a 0, and we start in EVEN state
PUT_BIT(0);
break;
default:
break;
}
while (n) {
s = mfm_io_read_symbol(io);
PUT_BIT(state); // 'even' is 1, so record a '1' or '0' as appropriate
if (s == pulse_1000) {
PUT_BIT(0); // the other bit recorded for a 1000 is always a '0'
}
if (s == pulse_100) {
if (state) {
PUT_BIT(0);
} // If 'even', record an additional '0'
state = (mfm_state_t)!state; // the next symbol has opposite parity
}
*buf = tmp >> 8; // store every time to make timing more even
if (weight <= 0x80) {
tmp <<= 8;
weight <<= 8;
buf++;
n--;
}
}
}
// Perform all the steps of receiving the next IDAM, DAM (or DDAM, but we don't
// use them)
__attribute__((always_inline)) inline static bool
wait_triple_sync_mark_receive_crc(mfm_io_t *io, void *buf, size_t n) {
if (!wait_triple_sync_mark(io)) {
return false;
}
receive(io, (uint8_t *)buf, n);
unsigned crc = crc16_preloaded((uint8_t *)buf, n);
return crc == 0;
}
// Read a whole track, setting validity[] for each sector actually read, up to
// n_sectors indexing of validity & data is 0-based, even though IDAMs store
// sectors as 1-based
static int read_track(mfm_io_t io, int n_sectors, void *data,
uint8_t *validity) {
memset(validity, 0, n_sectors);
int n_valid = 0;
mfm_io_reset_sync_count(&io);
unsigned char buf[512 + 3];
while (mfm_io_get_sync_count(&io) < 3 && n_valid < n_sectors) {
if (!wait_triple_sync_mark_receive_crc(&io, buf, metadata_size)) {
continue;
}
if (buf[0] != IDAM) {
continue;
}
int r = (uint8_t)buf[3] - 1;
if (r >= n_sectors) {
continue;
}
if (validity[r]) {
continue;
}
if (!wait_triple_sync_mark_receive_crc(&io, buf, sizeof(buf))) {
continue;
}
if (buf[0] != DAM) {
continue;
}
memcpy((char *)data + blocksize * r, buf + 1, blocksize);
validity[r] = 1;
n_valid++;
}
return n_valid;
}
#if MFM_IO_MMIO
#define READ_DATA() (!!(*io->data_port & io->data_mask))
#define READ_INDEX() (!!(*io->index_port & io->index_mask))
__attribute__((optimize("O3"), always_inline)) static inline mfm_io_symbol_t
mfm_io_read_symbol(mfm_io_t *io) {
unsigned pulse_count = 3;
while (!READ_DATA()) {
pulse_count++;
}
unsigned index_state = (io->index_state << 1) | READ_INDEX();
if ((index_state & 3) == 2) { // a zero-to-one transition
io->index_count++;
}
io->index_state = index_state;
while (READ_DATA()) {
pulse_count++;
}
int result = pulse_10;
if (pulse_count > T2_5) {
result++;
}
if (pulse_count > T3_5) {
result++;
}
return (mfm_io_symbol_t)result;
}
static void mfm_io_reset_sync_count(mfm_io_t *io) { io->index_count = 0; }
__attribute__((optimize("O3"), always_inline)) inline static int
mfm_io_get_sync_count(mfm_io_t *io) {
return io->index_count;
}
#endif
#pragma GCC pop_options