aboutsummaryrefslogtreecommitdiff
path: root/circuitpython/lib/adafruit_floppy
diff options
context:
space:
mode:
Diffstat (limited to 'circuitpython/lib/adafruit_floppy')
-rw-r--r--circuitpython/lib/adafruit_floppy/LICENSES/CC-BY-4.0.txt156
-rw-r--r--circuitpython/lib/adafruit_floppy/LICENSES/CC0-1.0.txt121
-rw-r--r--circuitpython/lib/adafruit_floppy/LICENSES/MIT.txt9
-rw-r--r--circuitpython/lib/adafruit_floppy/Makefile14
-rw-r--r--circuitpython/lib/adafruit_floppy/README.md45
-rw-r--r--circuitpython/lib/adafruit_floppy/code-of-conduct.md127
-rw-r--r--circuitpython/lib/adafruit_floppy/examples/fat_test/fat_test.ino157
-rw-r--r--circuitpython/lib/adafruit_floppy/examples/floppy_capture_track_test/floppy_capture_track_test.ino112
-rw-r--r--circuitpython/lib/adafruit_floppy/examples/greaseweazle/greaseweazle.ino525
-rw-r--r--circuitpython/lib/adafruit_floppy/examples/mfm_test/mfm_test.ino138
-rw-r--r--circuitpython/lib/adafruit_floppy/examples/msd_test/msd_test.ino163
-rw-r--r--circuitpython/lib/adafruit_floppy/flux.txt1
-rw-r--r--circuitpython/lib/adafruit_floppy/flux.txt.license3
-rw-r--r--circuitpython/lib/adafruit_floppy/images/rabbit.pngbin0 -> 23110 bytes
-rw-r--r--circuitpython/lib/adafruit_floppy/images/readme1
-rw-r--r--circuitpython/lib/adafruit_floppy/library.properties10
-rw-r--r--circuitpython/lib/adafruit_floppy/license.txt10
-rw-r--r--circuitpython/lib/adafruit_floppy/mfm.license3
-rw-r--r--circuitpython/lib/adafruit_floppy/src/Adafruit_Floppy.cpp524
-rw-r--r--circuitpython/lib/adafruit_floppy/src/Adafruit_Floppy.h150
-rw-r--r--circuitpython/lib/adafruit_floppy/src/Adafruit_MFM_Floppy.cpp206
-rw-r--r--circuitpython/lib/adafruit_floppy/src/mfm_impl.h266
-rw-r--r--circuitpython/lib/adafruit_floppy/standalone/main.c49
23 files changed, 2790 insertions, 0 deletions
diff --git a/circuitpython/lib/adafruit_floppy/LICENSES/CC-BY-4.0.txt b/circuitpython/lib/adafruit_floppy/LICENSES/CC-BY-4.0.txt
new file mode 100644
index 0000000..13ca539
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/LICENSES/CC-BY-4.0.txt
@@ -0,0 +1,156 @@
+Creative Commons Attribution 4.0 International
+
+ Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
+
+Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors.
+
+Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public.
+
+Creative Commons Attribution 4.0 International Public License
+
+By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
+
+Section 1 – Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
+
+ c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
+
+ d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
+
+ e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
+
+ f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
+
+ g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
+
+ h. Licensor means the individual(s) or entity(ies) granting rights under this Public License.
+
+ i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
+
+ j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
+
+ k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
+
+Section 2 – Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
+
+ A. reproduce and Share the Licensed Material, in whole or in part; and
+
+ B. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section 6(a).
+
+ 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
+
+ B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
+
+b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this Public License.
+
+ 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.
+
+Section 3 – License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified form), You must:
+
+ A. retain the following if it is supplied by the Licensor with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
+
+ B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
+
+ C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
+
+ 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
+
+ 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License.
+
+Section 4 – Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and
+
+ c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
+For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
+
+Section 5 – Disclaimer of Warranties and Limitation of Liability.
+
+ a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.
+
+ b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.
+
+ c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
+
+Section 6 – Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
+
+ d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
+
+ e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
+
+Section 7 – Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
+
+Section 8 – Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
+
+ c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
+
+Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
diff --git a/circuitpython/lib/adafruit_floppy/LICENSES/CC0-1.0.txt b/circuitpython/lib/adafruit_floppy/LICENSES/CC0-1.0.txt
new file mode 100644
index 0000000..0e259d4
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/LICENSES/CC0-1.0.txt
@@ -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.
diff --git a/circuitpython/lib/adafruit_floppy/LICENSES/MIT.txt b/circuitpython/lib/adafruit_floppy/LICENSES/MIT.txt
new file mode 100644
index 0000000..2071b23
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/LICENSES/MIT.txt
@@ -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.
diff --git a/circuitpython/lib/adafruit_floppy/Makefile b/circuitpython/lib/adafruit_floppy/Makefile
new file mode 100644
index 0000000..3b6b2b6
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/Makefile
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+mfm: stand/main.c mfm_impl.h
+ gcc -iquote . -o $@ $<
+
+.PHONY: test
+test: mfm
+ ./mfm < flux.txt
+
+.PHONY: clean
+clean:
+ rm -f mfm
diff --git a/circuitpython/lib/adafruit_floppy/README.md b/circuitpython/lib/adafruit_floppy/README.md
new file mode 100644
index 0000000..9290f9f
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/README.md
@@ -0,0 +1,45 @@
+# Adafruit Floppy [![Build Status](https://github.com/adafruit/Adafruit_Floppy/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_Floppy/actions)
+
+![Adafruit Floppy](./images/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 will attempt to increase the availability and accessibility of floppy disk controllers by:
+
+1. **porting the greaseweazle / fluxengine firmware to Arduino** so that it is less tied to specific hardware. this is important as, during 2021 we learned that silicon shortages can make specific chips extremely difficult to find - having a cross-platform firmware alleviates dependancies on specific chips.
+
+2. **adding firmware support for the RP2040 chip / pico**. this is an ultra low cost dev board, at $4 each - and can make for an excellent alternative to higher cost atmel/stm chips. (of course, the firmware should be able to run on many chips, but we want to make sure this is one of them!)
+
+3. **adding hardware support for reading apple ii disks**. many flux readers focus on 34-pin disk drives but do not have interfacing for apple disk ii drives. the drives are available and could be used for archiving a vast number of floppies out there! this will require adding an index sensor so we can image disks into 'woz' formats. currently, applesauce hardware and software can do this for apple ii disks - applesauce is amazing and an excellent tool and we recommend it to folks! at this time, it appears to be closed source hardware, firmware and software, so we are not able to integrate their design into an open source design.
+
+4. **adding woz/a2r support to greaseweazle / fluxengine**. once hardware support is in place, we can then add woz/a2r file format support to the open source tools in existence, which will benefit the entire community
+
+5. as 'extra credit' we may look into **analog flux data acquisition methods** for repair of damaged disks.
+
+Any hardware, firmware, or software we write is going to be fully open source under permissive licenses such as MIT, BSD or Unlicense. we will probably sell accessories, assembled PCBs, cables, etc in the Adafruit shop to help get hardware into folks hands but the designs will always be re-createable by others without any licensing agreements, NDAs, or discussion.
+
+https://user-images.githubusercontent.com/1685947/147865571-c9ea1d68-6603-436d-9980-bc5ade148db8.mp4
+
+https://user-images.githubusercontent.com/1685947/147864181-c5885b15-1809-4e54-8680-4cfba3f54faa.mp4
+
+Latest video Jan 1, 2022 - 9pm EDT
+
+Currently we are focusing on high-RAM (> 128KB SRAM) and high speed (> 100MHz) processors, so that we can buffer a full track of flux transitions at once, and not require the use of special peripherals such as timers. (Of course, those are welcome later!)
+
+Tested working on:
+ * SAMD51 chipset hardware - Please overclock to 180MHz, select Fastest optimization, and use TinyUSB stack for best performance
+ * RP2040 chipset hardware - Please use philhower core, overclock to 200MHz, and select -O3 optimization for best performance
+Longer version!
+
+## Frequently Asked/Accused Questions
+
+There's a LOT of preconceptions about floppy disks and how / why we have this library. Here are some answers!
+
+* **How are you connecting a 3.3V logic microcontroller to a 5V Floppy Drive directly WITHOUT a level shifter, won't this destroy the board?**
+Floppy drives are powered by 5V, and they use open drain outputs. That means that if the microcontroller pulls the index pin (for example) high to 3.3V, the logic level will be 3V. Is this out of spec? Maybe! But it does seem to work. Of course its always polite to use a level shifter, so if you can please add it to your hardware design. We do recommend a stronger external pullup on the READDATA line, 4.7K or so seems fine, other lines are fine with an internal pullup.
+* **Did you know there are USB Floppy Drives for $10 on Amazon?** Yes, we are aware. These are recycled laptop floppy drives with a controller chip that presents a mass storage interface to the sectors on disk. They are great for basic access to 1.44MB IBM PC MFM-formatted diskettes. They will *not* work for GCR formatted diskettes (we know because we tried) and may not work with non-FAT formatted diskettes (we don't have any but the controller chip is very specialized and may freak out). USB floppy drive controllers will not get you flux-level readings, and can't cope with damaged diskettes to read sector data that does not pass CRC to perform data recovery. They are also, of course, no good with 5.25" floppy diskettes.
+* **Why do you need a flux level reading of disks???** Flux level readings are essential for data recovery, restoration, archiving of damaged or copy-protected floppies. You can read more about floppy disk preservation efforts here: https://wiki.archiveteam.org/index.php/Rescuing_Floppy_Disks
+
+Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from [Adafruit](https://adafruit.com)!
+
+MIT license, all text above must be included in any redistribution.
diff --git a/circuitpython/lib/adafruit_floppy/code-of-conduct.md b/circuitpython/lib/adafruit_floppy/code-of-conduct.md
new file mode 100644
index 0000000..8ee6e44
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/code-of-conduct.md
@@ -0,0 +1,127 @@
+# Adafruit Community Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and leaders pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level or type of
+experience, education, socio-economic status, nationality, personal appearance,
+race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+We are committed to providing a friendly, safe and welcoming environment for
+all.
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Be kind and courteous to others
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Collaborating with other community members
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and sexual attention or advances
+* The use of inappropriate images, including in a community member's avatar
+* The use of inappropriate language, including in a community member's nickname
+* Any spamming, flaming, baiting or other attention-stealing behavior
+* Excessive or unwelcome helping; answering outside the scope of the question
+ asked
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate
+
+The goal of the standards and moderation guidelines outlined here is to build
+and maintain a respectful community. We ask that you don’t just aim to be
+"technically unimpeachable", but rather try to be your best self.
+
+We value many things beyond technical expertise, including collaboration and
+supporting others within our community. Providing a positive experience for
+other community members can have a much more significant impact than simply
+providing the correct answer.
+
+## Our Responsibilities
+
+Project leaders are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project leaders have the right and responsibility to remove, edit, or
+reject messages, comments, commits, code, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any community member for other behaviors that they deem
+inappropriate, threatening, offensive, or harmful.
+
+## Moderation
+
+Instances of behaviors that violate the Adafruit Community Code of Conduct
+may be reported by any member of the community. Community members are
+encouraged to report these situations, including situations they witness
+involving other community members.
+
+You may report in the following ways:
+
+In any situation, you may send an email to <support@adafruit.com>.
+
+On the Adafruit Discord, you may send an open message from any channel
+to all Community Helpers by tagging @community helpers. You may also send an
+open message from any channel, or a direct message to @kattni#1507,
+@tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or
+@Andon#8175.
+
+Email and direct message reports will be kept confidential.
+
+In situations on Discord where the issue is particularly egregious, possibly
+illegal, requires immediate action, or violates the Discord terms of service,
+you should also report the message directly to Discord.
+
+These are the steps for upholding our community’s standards of conduct.
+
+1. Any member of the community may report any situation that violates the
+Adafruit Community Code of Conduct. All reports will be reviewed and
+investigated.
+2. If the behavior is an egregious violation, the community member who
+committed the violation may be banned immediately, without warning.
+3. Otherwise, moderators will first respond to such behavior with a warning.
+4. Moderators follow a soft "three strikes" policy - the community member may
+be given another chance, if they are receptive to the warning and change their
+behavior.
+5. If the community member is unreceptive or unreasonable when warned by a
+moderator, or the warning goes unheeded, they may be banned for a first or
+second offense. Repeated offenses will result in the community member being
+banned.
+
+## Scope
+
+This Code of Conduct and the enforcement policies listed above apply to all
+Adafruit Community venues. This includes but is not limited to any community
+spaces (both public and private), the entire Adafruit Discord server, and
+Adafruit GitHub repositories. Examples of Adafruit Community spaces include
+but are not limited to meet-ups, audio chats on the Adafruit Discord, or
+interaction at a conference.
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. As a community
+member, you are representing our community, and are expected to behave
+accordingly.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 1.4, available at
+<https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>,
+and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html).
+
+For other projects adopting the Adafruit Community Code of
+Conduct, please contact the maintainers of those projects for enforcement.
+If you wish to use this code of conduct for your own project, consider
+explicitly mentioning your moderation policy or making a copy with your
+own moderation policy so as to avoid confusion.
diff --git a/circuitpython/lib/adafruit_floppy/examples/fat_test/fat_test.ino b/circuitpython/lib/adafruit_floppy/examples/fat_test/fat_test.ino
new file mode 100644
index 0000000..f9ff9ff
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/examples/fat_test/fat_test.ino
@@ -0,0 +1,157 @@
+/*
+ * 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
+#if F_CPU != 180000000L
+ #warning "please set CPU speed to 180MHz overclock"
+#endif
+#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
+ #define DENSITY_PIN A0 // IDC 2
+ #define INDEX_PIN A1 // IDC 8
+ #define SELECT_PIN A2 // IDC 12
+ #define MOTOR_PIN A3 // IDC 16
+ #define DIR_PIN 24 // IDC 18
+ #define STEP_PIN 25 // IDC 20
+ #define WRDATA_PIN 13 // IDC 22 (not used during read)
+ #define WRGATE_PIN 12 // IDC 24 (not used during read)
+ #define TRK0_PIN 11 // IDC 26
+ #define PROT_PIN 10 // IDC 28
+ #define READ_PIN 9 // IDC 30
+ #define SIDE_PIN 8 // IDC 32
+ #define READY_PIN 7 // IDC 34
+#if F_CPU != 200000000L
+ #warning "please set CPU speed to 200MHz overclock"
+#endif
+#elif defined (ARDUINO_RASPBERRY_PI_PICO)
+ #define DENSITY_PIN 2 // IDC 2
+ #define INDEX_PIN 3 // IDC 8
+ #define SELECT_PIN 4 // IDC 12
+ #define MOTOR_PIN 5 // IDC 16
+ #define DIR_PIN 6 // IDC 18
+ #define STEP_PIN 7 // IDC 20
+ #define WRDATA_PIN 8 // IDC 22 (not used during read)
+ #define WRGATE_PIN 9 // IDC 24 (not used during read)
+ #define TRK0_PIN 10 // IDC 26
+ #define PROT_PIN 11 // IDC 28
+ #define READ_PIN 12 // IDC 30
+ #define SIDE_PIN 13 // IDC 32
+ #define READY_PIN 14 // IDC 34
+#if F_CPU != 200000000L
+ #warning "please set CPU speed to 200MHz overclock"
+#endif
+#else
+#error "Please set up pin definitions!"
+#endif
+
+Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
+ MOTOR_PIN, DIR_PIN, STEP_PIN,
+ WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
+ PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
+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);
+ }
+}
diff --git a/circuitpython/lib/adafruit_floppy/examples/floppy_capture_track_test/floppy_capture_track_test.ino b/circuitpython/lib/adafruit_floppy/examples/floppy_capture_track_test/floppy_capture_track_test.ino
new file mode 100644
index 0000000..2861b0c
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/examples/floppy_capture_track_test/floppy_capture_track_test.ino
@@ -0,0 +1,112 @@
+#include <Adafruit_Floppy.h>
+
+// If using SAMD51, turn on TINYUSB USB stack
+#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
+ #define DENSITY_PIN A0 // IDC 2
+ #define INDEX_PIN A1 // IDC 8
+ #define SELECT_PIN A2 // IDC 12
+ #define MOTOR_PIN A3 // IDC 16
+ #define DIR_PIN A4 // IDC 18
+ #define STEP_PIN A5 // IDC 20
+ #define WRDATA_PIN 13 // IDC 22 (not used during read)
+ #define WRGATE_PIN 12 // IDC 24 (not used during read)
+ #define TRK0_PIN 11 // IDC 26
+ #define PROT_PIN 10 // IDC 28
+ #define READ_PIN 9 // IDC 30
+ #define SIDE_PIN 6 // IDC 32
+ #define READY_PIN 5 // IDC 34
+#if F_CPU != 180000000L
+ #warning "please set CPU speed to 180MHz overclock"
+#endif
+#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
+ #define DENSITY_PIN A0 // IDC 2
+ #define INDEX_PIN A1 // IDC 8
+ #define SELECT_PIN A2 // IDC 12
+ #define MOTOR_PIN A3 // IDC 16
+ #define DIR_PIN 24 // IDC 18
+ #define STEP_PIN 25 // IDC 20
+ #define WRDATA_PIN 13 // IDC 22 (not used during read)
+ #define WRGATE_PIN 12 // IDC 24 (not used during read)
+ #define TRK0_PIN 11 // IDC 26
+ #define PROT_PIN 10 // IDC 28
+ #define READ_PIN 9 // IDC 30
+ #define SIDE_PIN 6 // IDC 32
+ #define READY_PIN 5 // IDC 34
+#if F_CPU != 200000000L
+ #warning "please set CPU speed to 200MHz overclock"
+#endif
+#elif defined (ARDUINO_RASPBERRY_PI_PICO)
+ #define DENSITY_PIN 2 // IDC 2
+ #define INDEX_PIN 3 // IDC 8
+ #define SELECT_PIN 4 // IDC 12
+ #define MOTOR_PIN 5 // IDC 16
+ #define DIR_PIN 6 // IDC 18
+ #define STEP_PIN 7 // IDC 20
+ #define WRDATA_PIN 8 // IDC 22 (not used during read)
+ #define WRGATE_PIN 9 // IDC 24 (not used during read)
+ #define TRK0_PIN 10 // IDC 26
+ #define PROT_PIN 11 // IDC 28
+ #define READ_PIN 12 // IDC 30
+ #define SIDE_PIN 13 // IDC 32
+ #define READY_PIN 14 // IDC 34
+#if F_CPU != 200000000L
+ #warning "please set CPU speed to 200MHz overclock"
+#endif
+#else
+#error "Please set up pin definitions!"
+#endif
+
+Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
+ MOTOR_PIN, DIR_PIN, STEP_PIN,
+ WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
+ PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
+
+// WARNING! there are 150K max flux pulses per track!
+uint8_t flux_transitions[MAX_FLUX_PULSE_PER_TRACK];
+
+uint32_t time_stamp = 0;
+
+
+void setup() {
+ Serial.begin(115200);
+ while (!Serial) delay(100);
+
+ Serial.println("its time for a nice floppy transfer!");
+ floppy.debug_serial = &Serial;
+ floppy.begin();
+
+ floppy.select(true);
+ if (! floppy.spin_motor(true)) {
+ Serial.println("Failed to spin up motor & find index pulse");
+ while (1) yield();
+ }
+
+ Serial.print("Seeking track...");
+ if (! floppy.goto_track(0)) {
+ Serial.println("Failed to seek to track");
+ while (1) yield();
+ }
+ Serial.println("done!");
+}
+
+void loop() {
+ uint32_t captured_flux = floppy.capture_track(flux_transitions, sizeof(flux_transitions));
+
+ Serial.print("Captured ");
+ Serial.print(captured_flux);
+ Serial.println(" flux transitions");
+
+ //floppy.print_pulses(flux_transitions, captured_flux);
+ floppy.print_pulse_bins(flux_transitions, captured_flux, 255);
+
+ if ((millis() - time_stamp) > 1000) {
+ Serial.print("Ready? ");
+ Serial.println(digitalRead(READY_PIN) ? "No" : "Yes");
+ Serial.print("Write Protected? ");
+ Serial.println(digitalRead(PROT_PIN) ? "No" : "Yes");
+ Serial.print("Track 0? ");
+ Serial.println(digitalRead(TRK0_PIN) ? "No" : "Yes");
+ time_stamp = millis();
+ }
+ yield();
+}
diff --git a/circuitpython/lib/adafruit_floppy/examples/greaseweazle/greaseweazle.ino b/circuitpython/lib/adafruit_floppy/examples/greaseweazle/greaseweazle.ino
new file mode 100644
index 0000000..5ed7a4b
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/examples/greaseweazle/greaseweazle.ino
@@ -0,0 +1,525 @@
+#include <Adafruit_Floppy.h>
+
+#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
+ #define DENSITY_PIN A0 // IDC 2
+ #define INDEX_PIN A1 // IDC 8
+ #define SELECT_PIN A2 // IDC 12
+ #define MOTOR_PIN A3 // IDC 16
+ #define DIR_PIN A4 // IDC 18
+ #define STEP_PIN A5 // IDC 20
+ #define WRDATA_PIN 13 // IDC 22
+ #define WRGATE_PIN 12 // IDC 24
+ #define TRK0_PIN 11 // IDC 26
+ #define PROT_PIN 10 // IDC 28
+ #define READ_PIN 9 // IDC 30
+ #define SIDE_PIN 6 // IDC 32
+ #define READY_PIN 5 // IDC 34
+#if F_CPU != 180000000L
+ #warning "please set CPU speed to 180MHz overclock"
+#endif
+ #define GW_SAMPLEFREQ (F_CPU * 11/90) // samd51 is sample rate of 22MHz at 180MHz OC
+#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
+ #define DENSITY_PIN A0 // IDC 2
+ #define INDEX_PIN A1 // IDC 8
+ #define SELECT_PIN A2 // IDC 12
+ #define MOTOR_PIN A3 // IDC 16
+ #define DIR_PIN 24 // IDC 18
+ #define STEP_PIN 25 // IDC 20
+ #define WRDATA_PIN 13 // IDC 22
+ #define WRGATE_PIN 12 // IDC 24
+ #define TRK0_PIN 11 // IDC 26
+ #define PROT_PIN 10 // IDC 28
+ #define READ_PIN 9 // IDC 30
+ #define SIDE_PIN 8 // IDC 32
+ #define READY_PIN 7 // IDC 34
+#if F_CPU != 200000000L
+ #warning "please set CPU speed to 200MHz overclock"
+#endif
+ #define GW_SAMPLEFREQ 26000000UL // 26mhz for rp2040
+#elif defined (ARDUINO_RASPBERRY_PI_PICO)
+ #define DENSITY_PIN 2 // IDC 2
+ #define INDEX_PIN 3 // IDC 8
+ #define SELECT_PIN 4 // IDC 12
+ #define MOTOR_PIN 5 // IDC 16
+ #define DIR_PIN 6 // IDC 18
+ #define STEP_PIN 7 // IDC 20
+ #define WRDATA_PIN 8 // IDC 22 (not used during read)
+ #define WRGATE_PIN 9 // IDC 24 (not used during read)
+ #define TRK0_PIN 10 // IDC 26
+ #define PROT_PIN 11 // IDC 28
+ #define READ_PIN 12 // IDC 30
+ #define SIDE_PIN 13 // IDC 32
+ #define READY_PIN 14 // IDC 34
+#if F_CPU != 200000000L
+ #warning "please set CPU speed to 200MHz overclock"
+#endif
+ #define GW_SAMPLEFREQ 26000000UL // 26mhz for rp2040
+#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);
+
+uint32_t time_stamp = 0;
+
+uint8_t cmd_buffer[32], reply_buffer[128];
+uint8_t cmd_buff_idx = 0;
+
+#define GW_FIRMVER_MAJOR 1
+#define GW_FIRMVER_MINOR 0
+#define GW_MAXCMD 21
+#define GW_HW_MODEL 8 // Adafruity
+#define GW_HW_SUBMODEL 0 // Adafruit Floppy Generic
+#define GW_USB_SPEED 0 // Full Speed
+
+#define GW_CMD_GETINFO 0
+#define GW_CMD_GETINFO_FIRMWARE 0
+#define GW_CMD_GETINFO_BANDWIDTH 1
+#define GW_CMD_SEEK 2
+#define GW_CMD_HEAD 3
+#define GW_CMD_SETPARAMS 4
+#define GW_CMD_GETPARAMS 5
+#define GW_CMD_GETPARAMS_DELAYS 0
+#define GW_CMD_MOTOR 6
+#define GW_CMD_READFLUX 7
+#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_SETPIN 15
+#define GW_CMD_RESET 16
+#define GW_CMD_SOURCEBYTES 18
+#define GW_CMD_SINKBYTES 19
+#define GW_CMD_GETPIN 20
+
+#define GW_ACK_OK (byte)0
+#define GW_ACK_BADCMD 1
+#define GW_ACK_NOINDEX 2
+#define GW_ACK_NOTRACK0 3
+#define GW_ACK_NOUNIT 7
+
+uint32_t timestamp = 0;
+
+void setup() {
+ Serial.begin(115200);
+ Serial1.begin(115200);
+ //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;
+
+ if (Serial.available() < 2) return 0;
+ buff[i++] = Serial.read();
+ buff[i++] = Serial.read();
+ // wait for remaining data
+ while (Serial.available() < (buff[1] - 2)) {
+ delay(1);
+ yield();
+ }
+ for (; i<buff[1]; i++) {
+ buff[i] = Serial.read();
+ }
+ return i;
+}
+
+uint32_t bandwidth_timer;
+float bytes_per_sec;
+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
+
+
+void loop() {
+ uint8_t cmd_len = get_cmd(cmd_buffer, sizeof(cmd_buffer));
+ if (!cmd_len) {
+ if ((millis() > timestamp) && ((millis()-timestamp) > 3000)) {
+ Serial1.println("Timed out waiting for command, resetting motor");
+ floppy.goto_track(0);
+ floppy.spin_motor(false);
+ motor_state = false;
+ floppy.select(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);
+
+ if (cmd == GW_CMD_GETINFO) {
+ Serial1.println("Get info");
+ uint8_t sub_cmd = cmd_buffer[2];
+ if (sub_cmd == GW_CMD_GETINFO_FIRMWARE) {
+ reply_buffer[i++] = GW_ACK_OK;
+ reply_buffer[i++] = GW_FIRMVER_MAJOR; // 1 byte
+ 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;
+ reply_buffer[i++] = GW_HW_MODEL;
+ reply_buffer[i++] = GW_HW_SUBMODEL;
+ reply_buffer[i++] = GW_USB_SPEED;
+ Serial.write(reply_buffer, 34);
+ }
+ else if (sub_cmd == GW_CMD_GETINFO_BANDWIDTH) {
+ reply_buffer[i++] = GW_ACK_OK;
+ uint32_t min_bytes = transfered_bytes;
+ uint32_t max_bytes = transfered_bytes;
+ uint32_t min_usec = bandwidth_timer * 1000;
+ uint32_t max_usec = bandwidth_timer * 1000;
+ // TODO What is this math supposed to be??
+
+ reply_buffer[i++] = min_bytes & 0xFF;
+ reply_buffer[i++] = min_bytes >> 8;
+ reply_buffer[i++] = min_bytes >> 16;
+ reply_buffer[i++] = min_bytes >> 24;
+ reply_buffer[i++] = min_usec & 0xFF;
+ reply_buffer[i++] = min_usec >> 8;
+ reply_buffer[i++] = min_usec >> 16;
+ reply_buffer[i++] = min_usec >> 24;
+ reply_buffer[i++] = max_bytes & 0xFF;
+ reply_buffer[i++] = max_bytes >> 8;
+ reply_buffer[i++] = max_bytes >> 16;
+ reply_buffer[i++] = max_bytes >> 24;
+ reply_buffer[i++] = max_usec & 0xFF;
+ reply_buffer[i++] = max_usec >> 8;
+ reply_buffer[i++] = max_usec >> 16;
+ reply_buffer[i++] = max_usec >> 24;
+
+ // TODO more?
+ Serial.write(reply_buffer, 34);
+ }
+ }
+
+ else if (cmd == GW_CMD_GETPARAMS) {
+ Serial1.println("Get params");
+ 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;
+ Serial.write(reply_buffer, 12);
+ }
+ }
+
+ else if (cmd == GW_CMD_RESET) {
+ Serial1.println("Soft reset");
+ floppy.soft_reset();
+ reply_buffer[i++] = GW_ACK_OK;
+ Serial.write(reply_buffer, 2);
+ }
+
+ else if (cmd == GW_CMD_SETBUSTYPE) {
+ uint8_t bustype = cmd_buffer[2];
+ Serial1.printf("Set bus type %d\n\r", bustype);
+ // TODO: whats the diff???
+ if (bustype == GW_CMD_SETBUSTYPE_IBM) {
+ reply_buffer[i++] = GW_ACK_OK;
+ }
+ else if (bustype == GW_CMD_SETBUSTYPE_SHUGART) {
+ floppy.bus_type = BUSTYPE_SHUGART;
+ reply_buffer[i++] = GW_ACK_OK;
+ } else {
+ reply_buffer[i++] = GW_ACK_BADCMD;
+ }
+ Serial.write(reply_buffer, 2);
+ }
+
+ else if (cmd == GW_CMD_SEEK) {
+ uint8_t track = cmd_buffer[2];
+ Serial1.printf("Seek track %d\n\r", track);
+ bool r = floppy.goto_track(track);
+ if (r) {
+ reply_buffer[i++] = GW_ACK_OK;
+ } else {
+ reply_buffer[i++] = GW_ACK_NOTRACK0;
+ }
+ Serial.write(reply_buffer, 2);
+ }
+
+ else if (cmd == GW_CMD_HEAD) {
+ uint8_t head = cmd_buffer[2];
+ Serial1.printf("Seek head %d\n\r", head);
+ floppy.side(head);
+ reply_buffer[i++] = GW_ACK_OK;
+ Serial.write(reply_buffer, 2);
+ }
+
+ else if (cmd == GW_CMD_MOTOR) {
+ uint8_t unit = cmd_buffer[2];
+ uint8_t state = cmd_buffer[3];
+ Serial1.printf("Turn motor %d %s\n\r", unit, state ? "on" : "off");
+ if (motor_state != state) { // we're in the opposite state
+ if (! floppy.spin_motor(state)) {
+ reply_buffer[i++] = GW_ACK_NOINDEX;
+ } else {
+ reply_buffer[i++] = GW_ACK_OK;
+ }
+ motor_state = state;
+ } else {
+ // our cached state is correct!
+ reply_buffer[i++] = GW_ACK_OK;
+ }
+ Serial.write(reply_buffer, 2);
+ }
+
+ else if (cmd == GW_CMD_SELECT) {
+ uint8_t sub_cmd = cmd_buffer[2];
+ Serial1.printf("Select drive %d\n\r", sub_cmd);
+ if (sub_cmd == 0) {
+ floppy.select(true);
+ reply_buffer[i++] = GW_ACK_OK;
+ } else {
+ reply_buffer[i++] = GW_ACK_NOUNIT;
+ }
+ Serial.write(reply_buffer, 2);
+ }
+
+ else if (cmd == GW_CMD_DESELECT) {
+ Serial1.printf("Deselect drive\n\r");
+ floppy.select(false);
+ reply_buffer[i++] = GW_ACK_OK;
+ Serial.write(reply_buffer, 2);
+ }
+
+ else if (cmd == GW_CMD_READFLUX) {
+ uint32_t flux_ticks;
+ uint16_t revs;
+ flux_ticks = cmd_buffer[5];
+ flux_ticks <<= 8;
+ flux_ticks |= cmd_buffer[4];
+ flux_ticks <<= 8;
+ flux_ticks |= cmd_buffer[3];
+ flux_ticks <<= 8;
+ flux_ticks |= cmd_buffer[2];
+ revs = cmd_buffer[7];
+ revs <<= 8;
+ revs |= cmd_buffer[6];
+ 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);
+ 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;
+ 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
+ Serial.write(reply_buffer, 6);
+
+ uint8_t *flux_ptr = flux_transitions;
+ while (captured_pulses) {
+ uint32_t to_send = min(captured_pulses, (uint32_t)256);
+ Serial.write(flux_ptr, to_send);
+ //Serial1.println(to_send);
+ flux_ptr += to_send;
+ captured_pulses -= to_send;
+ }
+ }
+
+ // send a final indexop
+ reply_buffer[0] = 0xFF;
+ 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
+ Serial.write(reply_buffer, 6);
+
+ // flush input, to account for fluxengine bug
+ while (Serial.available()) Serial.read();
+ Serial.write((byte)0);
+ }
+
+ else if (cmd == GW_CMD_GETFLUXSTATUS) {
+ Serial1.println("get flux status");
+ reply_buffer[i++] = GW_ACK_OK;
+ Serial.write(reply_buffer, 2);
+ }
+
+
+ else if (cmd == GW_CMD_SINKBYTES) {
+ uint32_t numbytes = 0;
+ uint32_t seed = 0;
+ numbytes |= cmd_buffer[5];
+ numbytes <<= 8;
+ numbytes |= cmd_buffer[4];
+ numbytes <<= 8;
+ numbytes |= cmd_buffer[3];
+ numbytes <<= 8;
+ numbytes |= cmd_buffer[2];
+ Serial1.printf("sink numbytes %d\n\r", numbytes);
+
+ seed |= cmd_buffer[9];
+ seed <<= 8;
+ seed |= cmd_buffer[8];
+ seed <<= 8;
+ seed |= cmd_buffer[7];
+ seed <<= 8;
+ seed |= cmd_buffer[6];
+ reply_buffer[i++] = GW_ACK_OK;
+ Serial.write(reply_buffer, 2);
+ yield();
+ bandwidth_timer = millis();
+ transfered_bytes = numbytes;
+ bytes_per_sec = numbytes;
+
+ while (numbytes != 0) {
+ uint32_t avail = Serial.available();
+ if (avail == 0) {
+ //Serial1.print("-");
+ yield();
+ continue;
+ }
+ //Serial1.printf("%lu avail, ", avail);
+ uint32_t to_read = min(numbytes, min((uint32_t)sizeof(reply_buffer), avail));
+ //Serial1.printf("%lu to read, ", to_read);
+ numbytes -= Serial.readBytes((char *)reply_buffer, to_read);
+ //Serial1.printf("%lu remain\n\r", numbytes);
+ }
+ bandwidth_timer = millis() - bandwidth_timer;
+ bytes_per_sec /= bandwidth_timer;
+ bytes_per_sec *= 1000;
+ Serial1.print("Done in ");
+ Serial1.print(bandwidth_timer);
+ Serial1.print(" ms, ");
+ Serial1.print(bytes_per_sec);
+ Serial1.println(" bytes per sec");
+ Serial.write(GW_ACK_OK);
+ yield();
+ }
+ else if (cmd == GW_CMD_SOURCEBYTES) {
+ uint32_t numbytes = 0;
+ uint32_t seed = 0;
+ numbytes |= cmd_buffer[5];
+ numbytes <<= 8;
+ numbytes |= cmd_buffer[4];
+ numbytes <<= 8;
+ numbytes |= cmd_buffer[3];
+ numbytes <<= 8;
+ numbytes |= cmd_buffer[2];
+ Serial1.printf("source numbytes %d\n\r", numbytes);
+
+ seed |= cmd_buffer[9];
+ seed <<= 8;
+ seed |= cmd_buffer[8];
+ seed <<= 8;
+ seed |= cmd_buffer[7];
+ seed <<= 8;
+ seed |= cmd_buffer[6];
+ reply_buffer[i++] = GW_ACK_OK;
+ Serial.write(reply_buffer, 2);
+ yield();
+ bandwidth_timer = millis();
+ bytes_per_sec = numbytes;
+ transfered_bytes = numbytes;
+
+ uint32_t randnum = seed;
+ while (numbytes != 0) {
+ uint32_t to_write = min(numbytes, sizeof(reply_buffer));
+ // we dont write 'just anything'!
+ for (uint32_t i=0; i<to_write; i++) {
+ reply_buffer[i] = randnum;
+ if (randnum & 0x01) {
+ randnum = (randnum >> 1) ^ 0x80000062;
+ } else {
+ randnum >>= 1;
+ }
+ }
+ numbytes -= Serial.write(reply_buffer, to_write);
+ }
+ bandwidth_timer = millis() - bandwidth_timer;
+ bytes_per_sec /= bandwidth_timer;
+ bytes_per_sec *= 1000;
+ Serial1.print("Done in ");
+ Serial1.print(bandwidth_timer);
+ Serial1.print(" ms, ");
+ Serial1.print(bytes_per_sec);
+ Serial1.println(" bytes per sec");
+ } else if (cmd == GW_CMD_GETPIN) {
+ uint32_t pin = cmd_buffer[2];
+ Serial1.printf("getpin %d\n\r", pin);
+
+ switch(pin) {
+ case 26:
+ reply_buffer[i++] = GW_ACK_OK;
+ reply_buffer[i++] = digitalRead(TRK0_PIN);
+ break;
+
+ default:
+ // unknown pin, don't pretend we did it right
+ reply_buffer[i++] = GW_ACK_BADCMD;
+ reply_buffer[i++] = 0;
+ }
+ Serial.write(reply_buffer, i);
+ } else if (cmd == GW_CMD_SETPIN) {
+ uint32_t pin = cmd_buffer[2];
+ bool value = cmd_buffer[3];
+ Serial1.printf("setpin %d to \n\r", pin, value);
+
+ switch(pin) {
+ case 2:
+ pinMode(DENSITY_PIN, OUTPUT);
+ digitalWrite(DENSITY_PIN, value);
+ reply_buffer[i++] = GW_ACK_OK;
+ break;
+
+ default:
+ // unknown pin, don't pretend we did it right
+ reply_buffer[i++] = GW_ACK_BADCMD;
+ }
+
+ Serial.write(reply_buffer, i);
+
+ /********** unknown ! ********/
+ } else {
+ reply_buffer[i++] = GW_ACK_BADCMD;
+ Serial.write(reply_buffer, 2);
+ }
+ //Serial1.println("cmd complete!");
+} \ No newline at end of file
diff --git a/circuitpython/lib/adafruit_floppy/examples/mfm_test/mfm_test.ino b/circuitpython/lib/adafruit_floppy/examples/mfm_test/mfm_test.ino
new file mode 100644
index 0000000..07206f1
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/examples/mfm_test/mfm_test.ino
@@ -0,0 +1,138 @@
+#include <Adafruit_Floppy.h>
+
+
+
+// If using SAMD51, turn on TINYUSB USB stack
+#if defined(ADAFRUIT_FEATHER_M4_EXPRESS)
+ #define DENSITY_PIN A0 // IDC 2
+ #define INDEX_PIN A1 // IDC 8
+ #define SELECT_PIN A2 // IDC 12
+ #define MOTOR_PIN A3 // IDC 16
+ #define DIR_PIN A4 // IDC 18
+ #define STEP_PIN A5 // IDC 20
+ #define WRDATA_PIN 13 // IDC 22 (not used during read)
+ #define WRGATE_PIN 12 // IDC 24 (not used during read)
+ #define TRK0_PIN 11 // IDC 26
+ #define PROT_PIN 10 // IDC 28
+ #define READ_PIN 9 // IDC 30
+ #define SIDE_PIN 6 // IDC 32
+ #define READY_PIN 5 // IDC 34
+#if F_CPU != 180000000L
+ #warning "please set CPU speed to 180MHz overclock"
+#endif
+#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
+ #define DENSITY_PIN A0 // IDC 2
+ #define INDEX_PIN A1 // IDC 8
+ #define SELECT_PIN A2 // IDC 12
+ #define MOTOR_PIN A3 // IDC 16
+ #define DIR_PIN 24 // IDC 18
+ #define STEP_PIN 25 // IDC 20
+ #define WRDATA_PIN 13 // IDC 22 (not used during read)
+ #define WRGATE_PIN 12 // IDC 24 (not used during read)
+ #define TRK0_PIN 11 // IDC 26
+ #define PROT_PIN 10 // IDC 28
+ #define READ_PIN 9 // IDC 30
+ #define SIDE_PIN 8 // IDC 32
+ #define READY_PIN 7 // IDC 34
+#if F_CPU != 200000000L
+ #warning "please set CPU speed to 200MHz overclock"
+#endif
+#elif defined (ARDUINO_RASPBERRY_PI_PICO)
+ #define DENSITY_PIN 2 // IDC 2
+ #define INDEX_PIN 3 // IDC 8
+ #define SELECT_PIN 4 // IDC 12
+ #define MOTOR_PIN 5 // IDC 16
+ #define DIR_PIN 6 // IDC 18
+ #define STEP_PIN 7 // IDC 20
+ #define WRDATA_PIN 8 // IDC 22 (not used during read)
+ #define WRGATE_PIN 9 // IDC 24 (not used during read)
+ #define TRK0_PIN 10 // IDC 26
+ #define PROT_PIN 11 // IDC 28
+ #define READ_PIN 12 // IDC 30
+ #define SIDE_PIN 13 // IDC 32
+ #define READY_PIN 14 // IDC 34
+#if F_CPU != 200000000L
+ #warning "please set CPU speed to 200MHz overclock"
+#endif
+#else
+#error "Please set up pin definitions!"
+#endif
+
+Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
+ MOTOR_PIN, DIR_PIN, STEP_PIN,
+ WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
+ PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
+
+// 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);
+} \ No newline at end of file
diff --git a/circuitpython/lib/adafruit_floppy/examples/msd_test/msd_test.ino b/circuitpython/lib/adafruit_floppy/examples/msd_test/msd_test.ino
new file mode 100644
index 0000000..ab407cd
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/examples/msd_test/msd_test.ino
@@ -0,0 +1,163 @@
+// 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
+#if F_CPU != 180000000L
+ #warning "please set CPU speed to 180MHz overclock"
+#endif
+#elif defined (ARDUINO_ADAFRUIT_FEATHER_RP2040)
+ #define DENSITY_PIN A0 // IDC 2
+ #define INDEX_PIN A1 // IDC 8
+ #define SELECT_PIN A2 // IDC 12
+ #define MOTOR_PIN A3 // IDC 16
+ #define DIR_PIN 24 // IDC 18
+ #define STEP_PIN 25 // IDC 20
+ #define WRDATA_PIN 13 // IDC 22 (not used during read)
+ #define WRGATE_PIN 12 // IDC 24 (not used during read)
+ #define TRK0_PIN 11 // IDC 26
+ #define PROT_PIN 10 // IDC 28
+ #define READ_PIN 9 // IDC 30
+ #define SIDE_PIN 8 // IDC 32
+ #define READY_PIN 7 // IDC 34
+#if F_CPU != 200000000L
+ #warning "please set CPU speed to 200MHz overclock"
+#endif
+#elif defined (ARDUINO_RASPBERRY_PI_PICO)
+ #define DENSITY_PIN 2 // IDC 2
+ #define INDEX_PIN 3 // IDC 8
+ #define SELECT_PIN 4 // IDC 12
+ #define MOTOR_PIN 5 // IDC 16
+ #define DIR_PIN 6 // IDC 18
+ #define STEP_PIN 7 // IDC 20
+ #define WRDATA_PIN 8 // IDC 22 (not used during read)
+ #define WRGATE_PIN 9 // IDC 24 (not used during read)
+ #define TRK0_PIN 10 // IDC 26
+ #define PROT_PIN 11 // IDC 28
+ #define READ_PIN 12 // IDC 30
+ #define SIDE_PIN 13 // IDC 32
+ #define READY_PIN 14 // IDC 34
+#if F_CPU != 200000000L
+ #warning "please set CPU speed to 200MHz overclock"
+#endif
+#else
+#error "Please set up pin definitions!"
+#endif
+
+Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN,
+ MOTOR_PIN, DIR_PIN, STEP_PIN,
+ WRDATA_PIN, WRGATE_PIN, TRK0_PIN,
+ PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN);
+
+// 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
+} \ No newline at end of file
diff --git a/circuitpython/lib/adafruit_floppy/flux.txt b/circuitpython/lib/adafruit_floppy/flux.txt
new file mode 100644
index 0000000..48bcc4d
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/flux.txt
@@ -0,0 +1 @@
+011100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100111100100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101212012120121200000101110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012121021210212100000001000000000000000000000011000012011211020002110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012121021210212100000200002201100011111000102022022020110110011011200110110110100120021000000000000001100000011000001100000000000012001000000000000110000000012000001000000111100000000011110000000000000110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012110111102100111101002010111110011110001110000111001110001211022101211100000110000011000001100001101011100012221001010111011101100000110000011000000010010010000020000112020200001122010011012010000010021000012022202202100001002200201000100100000010110210110001200020022000001000010112001111011021012020110210101100022000000001122101021001021120011011100001021120011011100001020011020002002100110000101001111000010101110200020200020021010100120101110201010112111000010111010211200110202201120010110000011000012100010201010112201001200110201121110000102112020011001102011220011110021001100001010011110000101011102000202000200210101001201011102010101121110000101101102010102000200100010010001000111110000101001202001101110000102100121100100010011110112200110200110111000010100120200110001111100001022020112200011111000010021010200011100001002101001111000111110000101001201100201001202112020010110000012001012001012001011000000010210012100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000122202222210120222211001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001111001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001212102121021210000000100000000000000000000011000001211000011000101110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110011110010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012121021210212100000200000100100000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file
diff --git a/circuitpython/lib/adafruit_floppy/flux.txt.license b/circuitpython/lib/adafruit_floppy/flux.txt.license
new file mode 100644
index 0000000..4095538
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/flux.txt.license
@@ -0,0 +1,3 @@
+SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries
+
+SPDX-License-Identifier: CC0-1.0
diff --git a/circuitpython/lib/adafruit_floppy/images/rabbit.png b/circuitpython/lib/adafruit_floppy/images/rabbit.png
new file mode 100644
index 0000000..b7333ac
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/images/rabbit.png
Binary files differ
diff --git a/circuitpython/lib/adafruit_floppy/images/readme b/circuitpython/lib/adafruit_floppy/images/readme
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/images/readme
@@ -0,0 +1 @@
+
diff --git a/circuitpython/lib/adafruit_floppy/library.properties b/circuitpython/lib/adafruit_floppy/library.properties
new file mode 100644
index 0000000..36cc484
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/library.properties
@@ -0,0 +1,10 @@
+name=Adafruit Floppy
+version=0.1.0
+author=Adafruit
+maintainer=Adafruit <info@adafruit.com>
+sentence=Adafruit's floppy disk drive interfacing library
+paragraph=Adafruit's floppy disk drive interfacing library
+category=Communication
+url=https://github.com/adafruit/Adafruit_Floppy
+architectures=*
+depends=Adafruit BusIO, SdFat - Adafruit Fork
diff --git a/circuitpython/lib/adafruit_floppy/license.txt b/circuitpython/lib/adafruit_floppy/license.txt
new file mode 100644
index 0000000..6fb2072
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/license.txt
@@ -0,0 +1,10 @@
+Software License Agreement (MIT License)
+
+Copyright (c) 2021, Limor Fried for Adafruit Industries
+All rights reserved.
+
+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. \ No newline at end of file
diff --git a/circuitpython/lib/adafruit_floppy/mfm.license b/circuitpython/lib/adafruit_floppy/mfm.license
new file mode 100644
index 0000000..c8f7560
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/mfm.license
@@ -0,0 +1,3 @@
+SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries
+
+SPDX-License-Identifier: MIT \ No newline at end of file
diff --git a/circuitpython/lib/adafruit_floppy/src/Adafruit_Floppy.cpp b/circuitpython/lib/adafruit_floppy/src/Adafruit_Floppy.cpp
new file mode 100644
index 0000000..eef1aa0
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/src/Adafruit_Floppy.cpp
@@ -0,0 +1,524 @@
+#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
+
+uint32_t T2_5 = T2_5_IBMPC_HD;
+uint32_t T3_5 = T3_5_IBMPC_HD;
+
+#if !DEBUG_FLOPPY
+#undef set_debug_led
+#undef clr_debug_led
+#define set_debug_led() ((void)0)
+#define clr_debug_led() ((void)0)
+#endif
+
+#define MFM_IO_MMIO (1)
+#include "mfm_impl.h"
+
+/**************************************************************************/
+/*!
+ @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);
+
+ // set high density
+ pinMode(_densitypin, OUTPUT);
+ digitalWrite(_densitypin, LOW);
+
+ // set write OFF
+ if (_wrdatapin >= 0) {
+ pinMode(_wrdatapin, OUTPUT);
+ digitalWrite(_wrdatapin, HIGH);
+ }
+ if (_wrgatepin >= 0) {
+ pinMode(_wrgatepin, OUTPUT);
+ digitalWrite(_wrgatepin, HIGH);
+ }
+
+#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 = 100;
+ while (max_steps--) {
+ if (!digitalRead(_track0pin)) {
+ _track = 0;
+ break;
+ }
+ step(STEP_OUT, 1);
+ }
+
+ if (digitalRead(_track0pin)) {
+ // we never got a track 0 indicator :(
+ // what if we try stepping in a bit??
+
+ max_steps = 20;
+ while (max_steps--) {
+ if (!digitalRead(_track0pin)) {
+ _track = 0;
+ break;
+ }
+ step(STEP_IN, 1);
+ }
+
+ if (digitalRead(_track0pin)) {
+ // STILL not found!
+ 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, FLOPPY_IBMPC_HD_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);
+ delay((step_delay_us / 1000UL) + 1); // round up to at least 1ms
+ digitalWrite(_steppin, LOW);
+ delay((step_delay_us / 1000UL) + 1);
+ digitalWrite(_steppin, HIGH); // end high
+ yield();
+ }
+ // one more for good measure (5.25" drives seemed to like this)
+ delay((step_delay_us / 1000UL) + 1);
+}
+
+/**************************************************************************/
+/*!
+ @brief The current track location, based on internal caching
+ @return The cached track location
+*/
+/**************************************************************************/
+int8_t Adafruit_Floppy::track(void) { return _track; }
+
+/**************************************************************************/
+/*!
+ @brief Capture and decode one track of MFM data
+ @param sectors A pointer to an array of memory we can use to store into,
+ 512*n_sectors bytes
+ @param n_sectors The number of sectors (e.g., 18 for a
+ standard 3.5", 1.44MB format)
+ @param sector_validity An array of values set to 1 if the sector was
+ captured, 0 if not captured (no IDAM, CRC error, etc)
+ @return Number of sectors we actually captured
+*/
+/**************************************************************************/
+uint32_t Adafruit_Floppy::read_track_mfm(uint8_t *sectors, size_t n_sectors,
+ uint8_t *sector_validity) {
+ mfm_io_t io;
+#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);
+ (void)ledPort;
+ (void)ledMask;
+ io.index_port = indexPort;
+ io.index_mask = indexMask;
+ io.data_port = dataPort;
+ io.data_mask = dataMask;
+#elif defined(ARDUINO_ARCH_RP2040)
+ io.index_port = &sio_hw->gpio_in;
+ io.index_mask = 1u << _indexpin;
+ io.data_port = &sio_hw->gpio_in;
+ io.data_mask = 1u << _rddatapin;
+#endif
+
+ noInterrupts();
+ int result = read_track(io, n_sectors, sectors, sector_validity);
+ interrupts();
+
+ return result;
+}
+
+/**************************************************************************/
+/*!
+ @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);
+ (void)ledPort;
+ (void)ledMask;
+#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(255u, 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]);
+ }
+ }
+ }
+}
diff --git a/circuitpython/lib/adafruit_floppy/src/Adafruit_Floppy.h b/circuitpython/lib/adafruit_floppy/src/Adafruit_Floppy.h
new file mode 100644
index 0000000..3e519eb
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/src/Adafruit_Floppy.h
@@ -0,0 +1,150 @@
+#ifndef ADAFRUIT_FLOPPY_H
+#define ADAFRUIT_FLOPPY_H
+
+#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
+
+#ifdef BUSIO_USE_FAST_PINIO
+#define FLOPPYIO_SAMPLERATE (F_CPU * 11u / 90u) // empirical on SAM D51 @ 120MHz
+#endif
+
+#if defined(ARDUINO_ARCH_RP2040)
+#undef FLOPPYIO_SAMPLERATE
+#define FLOPPYIO_SAMPLERATE (F_CPU * 13u / 100u) // empirical on RP2040 @ 200MHz
+#endif
+
+#define T2_5_IBMPC_HD (FLOPPYIO_SAMPLERATE * 5 / 2 / 1000000)
+#define T3_5_IBMPC_HD (FLOPPYIO_SAMPLERATE * 7 / 2 / 1000000)
+#define T2_5_IBMPC_DD (T2_5_IBMPC_HD * 2)
+#define T3_5_IBMPC_DD (T3_5_IBMPC_HD * 2)
+
+#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 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 read_track_mfm(uint8_t *sectors, size_t n_sectors,
+ uint8_t *sector_validity);
+ 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
+};
+
+/**************************************************************************/
+/*!
+ 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:
+ uint8_t _sectors_per_track = 0;
+ uint8_t _tracks_per_side = 0;
+ int8_t _last_track_read = -1; // last cached track
+ Adafruit_Floppy *_floppy = NULL;
+ adafruit_floppy_disk_t _format = IBMPC1440K;
+};
+
+#endif
diff --git a/circuitpython/lib/adafruit_floppy/src/Adafruit_MFM_Floppy.cpp b/circuitpython/lib/adafruit_floppy/src/Adafruit_MFM_Floppy.cpp
new file mode 100644
index 0000000..6128a20
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/src/Adafruit_MFM_Floppy.cpp
@@ -0,0 +1,206 @@
+#include <Adafruit_Floppy.h>
+
+extern uint32_t T2_5, T3_5;
+
+/**************************************************************************/
+/*!
+ @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;
+ T2_5 = T2_5_IBMPC_HD;
+ T3_5 = T3_5_IBMPC_HD;
+ } else if (_format == IBMPC360K) {
+ _sectors_per_track = MFM_IBMPC360K_SECTORS_PER_TRACK;
+ _tracks_per_side = FLOPPY_IBMPC_DD_TRACKS;
+ T2_5 = T2_5_IBMPC_DD;
+ T3_5 = T3_5_IBMPC_DD;
+ }
+}
+
+/**************************************************************************/
+/*!
+ @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);
+ /*
+ 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; }
diff --git a/circuitpython/lib/adafruit_floppy/src/mfm_impl.h b/circuitpython/lib/adafruit_floppy/src/mfm_impl.h
new file mode 100644
index 0000000..bfbe7ba
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/src/mfm_impl.h
@@ -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
diff --git a/circuitpython/lib/adafruit_floppy/standalone/main.c b/circuitpython/lib/adafruit_floppy/standalone/main.c
new file mode 100644
index 0000000..fee8142
--- /dev/null
+++ b/circuitpython/lib/adafruit_floppy/standalone/main.c
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries
+//
+// SPDX-License-Identifier: MIT
+
+#include <stdio.h>
+
+struct mfm_io {};
+
+#include "mfm_impl.h"
+
+void hexdump(void *buf_in, size_t n) {
+ unsigned char *buf = buf_in;
+ size_t i = 0;
+ for (; i < n; i++) {
+ if (i % 16 == 0) {
+ printf("%04zx ", i);
+ }
+ printf("%02x%c", buf[i], (i % 16 == 15) ? '\n' : ' ');
+ }
+ if (i % 16 != 0) {
+ putchar('\n');
+ }
+}
+
+static inline mfm_io_symbol_t mfm_io_read_symbol(mfm_io_t *io) {
+ int c = getchar();
+ return (mfm_io_symbol_t)(c - '0');
+}
+
+static void mfm_io_reset_sync_count(mfm_io_t *io) {}
+
+static inline int mfm_io_get_sync_count(mfm_io_t *io) {
+ return feof(stdin) ? 2 : 0;
+}
+
+int main() {
+ enum { n_sectors = 18 };
+ char data[n_sectors * blocksize];
+ uint8_t validity[n_sectors];
+ struct mfm_io io;
+ read_track(io, n_sectors, data, validity);
+ for (int i = 0; i < n_sectors; i++) {
+ printf("validity[% 2d] = %d\n", i, validity[i]);
+ if (validity[i]) {
+ hexdump(data + i * blocksize, blocksize);
+ printf("\n");
+ }
+ }
+}