├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── githubci.yml ├── .gitignore ├── .gitmodules ├── Doxyfile ├── LICENSES ├── CC-BY-4.0.txt ├── CC0-1.0.txt └── MIT.txt ├── README.md ├── code-of-conduct.md ├── examples ├── 01_floppy_capture_track_test │ └── 01_floppy_capture_track_test.ino ├── 02_mfm_test │ └── 02_mfm_test.ino ├── 03_fat_test │ └── 03_fat_test.ino ├── 04_msd_test │ ├── .feather_m4_express_tinyusb.generate │ ├── .feather_rp2040_tinyusb.generate │ ├── .feather_rp2350_tinyusb.generate │ ├── .floppsy_rp2040_tinyusb.generate │ ├── 04_msd_test.ino │ ├── display_common.h │ ├── display_floppsy.h │ ├── display_none.h │ └── display_state.h ├── 05_mfm_write_test │ └── 05_mfm_write_test.ino ├── 99_floppy_write_test │ └── 99_floppy_write_test.ino ├── apple2_test │ └── apple2_test.ino ├── greaseweazle │ ├── .feather_m4_express_tinyusb.generate │ ├── .feather_rp2040_tinyusb.generate │ ├── .feather_rp2350_tinyusb.generate │ ├── .floppsy_rp2040_tinyusb.generate │ └── greaseweazle.ino └── mfm_emu │ ├── .floppsy_rp2040_tinyusb.generate │ ├── .floppsy_rp2040_tinyusb.test.only │ ├── .gitignore │ ├── drive.pio │ ├── drive.pio.h │ ├── mfm_emu.ino │ └── tetros.h ├── host_src ├── .gitignore ├── Makefile ├── check_flux.py ├── main.c ├── main_fm.c ├── make_flux.py └── make_flux_fm.py ├── images ├── rabbit.png └── readme ├── library.properties ├── license.txt └── src ├── Adafruit_Floppy.cpp ├── Adafruit_Floppy.h ├── Adafruit_MFM_Floppy.cpp ├── arch_rp2.cpp ├── arch_rp2.h ├── arch_samd51.cpp ├── greasepack.h └── mfm_impl.h /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for opening an issue on an Adafruit Arduino library repository. To 2 | improve the speed of resolution please review the following guidelines and 3 | common troubleshooting steps below before creating the issue: 4 | 5 | - **Do not use GitHub issues for troubleshooting projects and issues.** Instead use 6 | the forums at http://forums.adafruit.com to ask questions and troubleshoot why 7 | something isn't working as expected. In many cases the problem is a common issue 8 | that you will more quickly receive help from the forum community. GitHub issues 9 | are meant for known defects in the code. If you don't know if there is a defect 10 | in the code then start with troubleshooting on the forum first. 11 | 12 | - **If following a tutorial or guide be sure you didn't miss a step.** Carefully 13 | check all of the steps and commands to run have been followed. Consult the 14 | forum if you're unsure or have questions about steps in a guide/tutorial. 15 | 16 | - **For Arduino projects check these very common issues to ensure they don't apply**: 17 | 18 | - For uploading sketches or communicating with the board make sure you're using 19 | a **USB data cable** and **not** a **USB charge-only cable**. It is sometimes 20 | very hard to tell the difference between a data and charge cable! Try using the 21 | cable with other devices or swapping to another cable to confirm it is not 22 | the problem. 23 | 24 | - **Be sure you are supplying adequate power to the board.** Check the specs of 25 | your board and plug in an external power supply. In many cases just 26 | plugging a board into your computer is not enough to power it and other 27 | peripherals. 28 | 29 | - **Double check all soldering joints and connections.** Flakey connections 30 | cause many mysterious problems. See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints. 31 | 32 | - **Ensure you are using an official Arduino or Adafruit board.** We can't 33 | guarantee a clone board will have the same functionality and work as expected 34 | with this code and don't support them. 35 | 36 | If you're sure this issue is a defect in the code and checked the steps above 37 | please fill in the following fields to provide enough troubleshooting information. 38 | You may delete the guideline and text above to just leave the following details: 39 | 40 | - Arduino board: **INSERT ARDUINO BOARD NAME/TYPE HERE** 41 | 42 | - Arduino IDE version (found in Arduino -> About Arduino menu): **INSERT ARDUINO 43 | VERSION HERE** 44 | 45 | - List the steps to reproduce the problem below (if possible attach a sketch or 46 | copy the sketch code in too): **LIST REPRO STEPS BELOW** 47 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for creating a pull request to contribute to Adafruit's GitHub code! 2 | Before you open the request please review the following guidelines and tips to 3 | help it be more easily integrated: 4 | 5 | - **Describe the scope of your change--i.e. what the change does and what parts 6 | of the code were modified.** This will help us understand any risks of integrating 7 | the code. 8 | 9 | - **Describe any known limitations with your change.** For example if the change 10 | doesn't apply to a supported platform of the library please mention it. 11 | 12 | - **Please run any tests or examples that can exercise your modified code.** We 13 | strive to not break users of the code and running tests/examples helps with this 14 | process. 15 | 16 | Thank you again for contributing! We will try to test and integrate the change 17 | as soon as we can, but be aware we have many GitHub repositories to manage and 18 | can't immediately respond to every request. There is no need to bump or check in 19 | on a pull request (it will clutter the discussion of the request). 20 | 21 | Also don't be worried if the request is closed or not integrated--sometimes the 22 | priorities of Adafruit's GitHub code (education, ease of use) might not match the 23 | priorities of the pull request. Don't fret, the open source community thrives on 24 | forks and GitHub makes it easy to keep your changes in a forked repo. 25 | 26 | After reviewing the guidelines above you can delete this text from the pull request. 27 | -------------------------------------------------------------------------------- /.github/workflows/githubci.yml: -------------------------------------------------------------------------------- 1 | name: Arduino Library CI 2 | 3 | on: [pull_request, push, repository_dispatch] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | arduino-platform: 11 | - feather_m4_express_tinyusb 12 | - feather_rp2040_tinyusb 13 | - floppsy_rp2040_tinyusb 14 | - feather_rp2350_tinyusb 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.x' 21 | - uses: actions/checkout@v4 22 | - uses: actions/checkout@v4 23 | with: 24 | repository: adafruit/ci-arduino 25 | path: ci 26 | 27 | - name: pre-install 28 | run: bash ci/actions_install.sh 29 | 30 | - name: fix SDFat 31 | run: git clone --quiet https://github.com/adafruit/SdFat.git /home/runner/Arduino/libraries/SdFat 32 | 33 | - name: test platforms 34 | run: python3 ci/build_platform.py ${{ matrix.arduino-platform }} 35 | 36 | - name: Move build artifacts into place 37 | run: | 38 | mkdir build 39 | find -name "*.uf2" -ls 40 | for i in examples/*/build/*/*.uf2; do j=${i##*/}; j=${j%%*.}; mv $i build/$j-${{ matrix.arduino-platform }}.uf2; done 41 | 42 | - name: Upload build artifacts 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: ${{ github.event.repository.name }}-${{ matrix.arduino-platform }} 46 | path: | 47 | build/*.uf2 48 | 49 | - name: Zip release files 50 | if: startsWith(github.ref, 'refs/tags/') 51 | run: | 52 | if [ -d build ]; then 53 | ( 54 | echo "Built from Adafruit Floppy `git describe --tags` for ${{ matrix.arduino-platform }}" 55 | echo "Source code: https://github.com/adafruit/Adafruit_Floppy" 56 | echo "Adafruit Learning System: https://learn.adafruit.com/" 57 | ) > build/README.txt 58 | cd build && zip -9 -o ${{ matrix.arduino-platform }}.zip *.hex *.bin *.uf2 *.txt 59 | fi 60 | 61 | - name: Create release 62 | if: startsWith(github.ref, 'refs/tags/') 63 | uses: softprops/action-gh-release@v1 64 | with: 65 | files: build/${{ matrix.arduino-platform }}.zip 66 | fail_on_unmatched_files: false 67 | body: "Select the zip file corresponding to your board from the list below." 68 | 69 | doxyclang: 70 | runs-on: ubuntu-latest 71 | 72 | steps: 73 | - uses: actions/setup-python@v5 74 | with: 75 | python-version: '3.x' 76 | - uses: actions/checkout@v4 77 | - uses: actions/checkout@v4 78 | with: 79 | repository: adafruit/ci-arduino 80 | path: ci 81 | 82 | - name: pre-install 83 | run: bash ci/actions_install.sh 84 | 85 | - name: fix SDFat 86 | run: git clone --quiet https://github.com/adafruit/SdFat.git /home/runner/Arduino/libraries/SdFat 87 | 88 | - name: clang 89 | run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -e "*.pio.h" -r . 90 | 91 | - name: doxygen 92 | env: 93 | GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }} 94 | PRETTYNAME : "Adafruit Floppy Interface Library" 95 | run: bash ci/doxy_gen_and_deploy.sh 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | /ci 6 | /doxygen 7 | /examples/*/build 8 | /html 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "doxygen-awesome-css"] 2 | path = doxygen-awesome-css 3 | url = https://github.com/jothepro/doxygen-awesome-css.git 4 | [submodule "host_src/greaseweazle"] 5 | path = host_src/greaseweazle 6 | url = https://github.com/keirf/greaseweazle.git 7 | -------------------------------------------------------------------------------- /LICENSES/CC-BY-4.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 4.0 International 2 | 3 | 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. 4 | 5 | Using Creative Commons Public Licenses 6 | 7 | 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. 8 | 9 | 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. 10 | 11 | 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. 12 | 13 | Creative Commons Attribution 4.0 International Public License 14 | 15 | 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. 16 | 17 | Section 1 – Definitions. 18 | 19 | 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. 20 | 21 | 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. 22 | 23 | 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. 24 | 25 | 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. 26 | 27 | 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. 28 | 29 | f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 30 | 31 | 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. 32 | 33 | h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. 34 | 35 | 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. 36 | 37 | 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. 38 | 39 | k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 40 | 41 | Section 2 – Scope. 42 | 43 | a. License grant. 44 | 45 | 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: 46 | 47 | A. reproduce and Share the Licensed Material, in whole or in part; and 48 | 49 | B. produce, reproduce, and Share Adapted Material. 50 | 51 | 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. 52 | 53 | 3. Term. The term of this Public License is specified in Section 6(a). 54 | 55 | 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. 56 | 57 | 5. Downstream recipients. 58 | 59 | 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. 60 | 61 | 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. 62 | 63 | 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). 64 | 65 | b. Other rights. 66 | 67 | 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. 68 | 69 | 2. Patent and trademark rights are not licensed under this Public License. 70 | 71 | 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. 72 | 73 | Section 3 – License Conditions. 74 | 75 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 76 | 77 | a. Attribution. 78 | 79 | 1. If You Share the Licensed Material (including in modified form), You must: 80 | 81 | A. retain the following if it is supplied by the Licensor with the Licensed Material: 82 | 83 | 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); 84 | 85 | ii. a copyright notice; 86 | 87 | iii. a notice that refers to this Public License; 88 | 89 | iv. a notice that refers to the disclaimer of warranties; 90 | 91 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 92 | 93 | B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 94 | 95 | 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. 96 | 97 | 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. 98 | 99 | 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. 100 | 101 | 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. 102 | 103 | Section 4 – Sui Generis Database Rights. 104 | 105 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 106 | 107 | 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; 108 | 109 | 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 110 | 111 | 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. 112 | 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. 113 | 114 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 115 | 116 | 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. 117 | 118 | 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. 119 | 120 | 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. 121 | 122 | Section 6 – Term and Termination. 123 | 124 | 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. 125 | 126 | b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 127 | 128 | 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 129 | 130 | 2. upon express reinstatement by the Licensor. 131 | 132 | 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. 133 | 134 | 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. 135 | 136 | e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 137 | 138 | Section 7 – Other Terms and Conditions. 139 | 140 | a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 141 | 142 | 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. 143 | 144 | Section 8 – Interpretation. 145 | 146 | 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. 147 | 148 | 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. 149 | 150 | 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. 151 | 152 | 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. 153 | 154 | 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. 155 | 156 | Creative Commons may be contacted at creativecommons.org. 157 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Adafruit Floppy 2 | [![Build Status](https://github.com/adafruit/Adafruit_Floppy/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_Floppy/actions) 3 | 4 | ![Adafruit Floppy](./images/rabbit.png) 5 | 6 | This is a helper library to abstract away interfacing with floppy disk drives in a cross-platform and open source library. 7 | 8 | Adafruit Floppy is a project to make a flexible, full-stack, open source hardware/software device for reading, archiving, accessing and duplicating floppy disk media. It joins a family of open source hardware and software such as greaseweazle and fluxengine, and increases the availability and accessibility of floppy disk controllers by: 9 | 10 | 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. 11 | 12 | 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!) 13 | 14 | 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. 15 | 16 | 4. **adding a2r support to fluxengine** and [writing an open source a2r to woz converter](https://pypi.org/project/a2woz/), which will benefit the entire community. 17 | 18 | 5. as 'extra credit' we may look into **analog flux data acquisition methods** for repair of damaged disks. 19 | 20 | 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. 21 | 22 | Currently we are focusing on high-RAM (> 128KB SRAM) and high speed (> 100MHz) processors, so that we can buffer a full track of flux transitions at once. Initial versions proved that this could be done with GPIO only, and no use of special peripherals. However, for the greatest timing accuracy we now use the timer peripheral on SAMD51 and the PIO peripheral on RP2040. 23 | 24 | Tested working on: 25 | * SAMD51 chipset hardware - Overclock to 180MHz, select Fastest optimization, and use TinyUSB stack for best performance 26 | * RP2040 chipset hardware - Works with philhower core and TinyUSB stack. Overclock to 200MHz and select -O3 optimization for best performance 27 | 28 | 29 | ## Follow our progress! 30 | 31 | [We have a full playlist on YouTube of our progress!](https://www.youtube.com/playlist?list=PLjF7R1fz_OOWexf2WmY8cgM65ltPaAvyT), or check out some of these videos of the software at a very early stage of development: 32 | 33 | https://user-images.githubusercontent.com/1685947/147865571-c9ea1d68-6603-436d-9980-bc5ade148db8.mp4 34 | 35 | https://user-images.githubusercontent.com/1685947/147864181-c5885b15-1809-4e54-8680-4cfba3f54faa.mp4 36 | 37 | 38 | ## Frequently Asked/Accused Questions 39 | 40 | There's a LOT of preconceptions about floppy disks and how / why we have this library. Here are some answers! 41 | 42 | * **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?** 43 | 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. 44 | * **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. 45 | * **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 46 | 47 | 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)! 48 | 49 | MIT license, all text above must be included in any redistribution. 50 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Adafruit Community Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and leaders pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level or type of 9 | experience, education, socio-economic status, nationality, personal appearance, 10 | race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | We are committed to providing a friendly, safe and welcoming environment for 15 | all. 16 | 17 | Examples of behavior that contributes to creating a positive environment 18 | include: 19 | 20 | * Be kind and courteous to others 21 | * Using welcoming and inclusive language 22 | * Being respectful of differing viewpoints and experiences 23 | * Collaborating with other community members 24 | * Gracefully accepting constructive criticism 25 | * Focusing on what is best for the community 26 | * Showing empathy towards other community members 27 | 28 | Examples of unacceptable behavior by participants include: 29 | 30 | * The use of sexualized language or imagery and sexual attention or advances 31 | * The use of inappropriate images, including in a community member's avatar 32 | * The use of inappropriate language, including in a community member's nickname 33 | * Any spamming, flaming, baiting or other attention-stealing behavior 34 | * Excessive or unwelcome helping; answering outside the scope of the question 35 | asked 36 | * Trolling, insulting/derogatory comments, and personal or political attacks 37 | * Public or private harassment 38 | * Publishing others' private information, such as a physical or electronic 39 | address, without explicit permission 40 | * Other conduct which could reasonably be considered inappropriate 41 | 42 | The goal of the standards and moderation guidelines outlined here is to build 43 | and maintain a respectful community. We ask that you don’t just aim to be 44 | "technically unimpeachable", but rather try to be your best self. 45 | 46 | We value many things beyond technical expertise, including collaboration and 47 | supporting others within our community. Providing a positive experience for 48 | other community members can have a much more significant impact than simply 49 | providing the correct answer. 50 | 51 | ## Our Responsibilities 52 | 53 | Project leaders are responsible for clarifying the standards of acceptable 54 | behavior and are expected to take appropriate and fair corrective action in 55 | response to any instances of unacceptable behavior. 56 | 57 | Project leaders have the right and responsibility to remove, edit, or 58 | reject messages, comments, commits, code, issues, and other contributions 59 | that are not aligned to this Code of Conduct, or to ban temporarily or 60 | permanently any community member for other behaviors that they deem 61 | inappropriate, threatening, offensive, or harmful. 62 | 63 | ## Moderation 64 | 65 | Instances of behaviors that violate the Adafruit Community Code of Conduct 66 | may be reported by any member of the community. Community members are 67 | encouraged to report these situations, including situations they witness 68 | involving other community members. 69 | 70 | You may report in the following ways: 71 | 72 | In any situation, you may send an email to . 73 | 74 | On the Adafruit Discord, you may send an open message from any channel 75 | to all Community Helpers by tagging @community helpers. You may also send an 76 | open message from any channel, or a direct message to @kattni#1507, 77 | @tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or 78 | @Andon#8175. 79 | 80 | Email and direct message reports will be kept confidential. 81 | 82 | In situations on Discord where the issue is particularly egregious, possibly 83 | illegal, requires immediate action, or violates the Discord terms of service, 84 | you should also report the message directly to Discord. 85 | 86 | These are the steps for upholding our community’s standards of conduct. 87 | 88 | 1. Any member of the community may report any situation that violates the 89 | Adafruit Community Code of Conduct. All reports will be reviewed and 90 | investigated. 91 | 2. If the behavior is an egregious violation, the community member who 92 | committed the violation may be banned immediately, without warning. 93 | 3. Otherwise, moderators will first respond to such behavior with a warning. 94 | 4. Moderators follow a soft "three strikes" policy - the community member may 95 | be given another chance, if they are receptive to the warning and change their 96 | behavior. 97 | 5. If the community member is unreceptive or unreasonable when warned by a 98 | moderator, or the warning goes unheeded, they may be banned for a first or 99 | second offense. Repeated offenses will result in the community member being 100 | banned. 101 | 102 | ## Scope 103 | 104 | This Code of Conduct and the enforcement policies listed above apply to all 105 | Adafruit Community venues. This includes but is not limited to any community 106 | spaces (both public and private), the entire Adafruit Discord server, and 107 | Adafruit GitHub repositories. Examples of Adafruit Community spaces include 108 | but are not limited to meet-ups, audio chats on the Adafruit Discord, or 109 | interaction at a conference. 110 | 111 | This Code of Conduct applies both within project spaces and in public spaces 112 | when an individual is representing the project or its community. As a community 113 | member, you are representing our community, and are expected to behave 114 | accordingly. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 1.4, available at 120 | , 121 | and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html). 122 | 123 | For other projects adopting the Adafruit Community Code of 124 | Conduct, please contact the maintainers of those projects for enforcement. 125 | If you wish to use this code of conduct for your own project, consider 126 | explicitly mentioning your moderation policy or making a copy with your 127 | own moderation policy so as to avoid confusion. 128 | -------------------------------------------------------------------------------- /examples/01_floppy_capture_track_test/01_floppy_capture_track_test.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(ADAFRUIT_FEATHER_M4_EXPRESS) 4 | #define DENSITY_PIN A1 // IDC 2 5 | #define INDEX_PIN A5 // IDC 8 6 | #define SELECT_PIN A0 // IDC 12 7 | #define MOTOR_PIN A2 // IDC 16 8 | #define DIR_PIN A3 // IDC 18 9 | #define STEP_PIN A4 // IDC 20 10 | #define WRDATA_PIN 13 // IDC 22 11 | #define WRGATE_PIN 12 // IDC 24 12 | #define TRK0_PIN 10 // IDC 26 13 | #define PROT_PIN 11 // IDC 28 14 | #define READ_PIN 9 // IDC 30 15 | #define SIDE_PIN 6 // IDC 32 16 | #define READY_PIN 5 // IDC 34 17 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) || defined(ARDUINO_ADAFRUIT_FEATHER_RP2350_HSTX) 18 | #define DENSITY_PIN A1 // IDC 2 19 | #define INDEX_PIN 25 // IDC 8 20 | #define SELECT_PIN A0 // IDC 12 21 | #define MOTOR_PIN A2 // IDC 16 22 | #define DIR_PIN A3 // IDC 18 23 | #define STEP_PIN 24 // IDC 20 24 | #define WRDATA_PIN 13 // IDC 22 25 | #define WRGATE_PIN 12 // IDC 24 26 | #define TRK0_PIN 10 // IDC 26 27 | #define PROT_PIN 11 // IDC 28 28 | #define READ_PIN 9 // IDC 30 29 | #define SIDE_PIN 8 // IDC 32 30 | #define READY_PIN 7 // IDC 34 31 | #ifndef USE_TINYUSB 32 | #error "Please set Adafruit TinyUSB under Tools > USB Stack" 33 | #endif 34 | #elif defined(ARDUINO_RASPBERRY_PI_PICO) 35 | #define DENSITY_PIN 2 // IDC 2 36 | #define INDEX_PIN 3 // IDC 8 37 | #define SELECT_PIN 4 // IDC 12 38 | #define MOTOR_PIN 5 // IDC 16 39 | #define DIR_PIN 6 // IDC 18 40 | #define STEP_PIN 7 // IDC 20 41 | #define WRDATA_PIN 8 // IDC 22 (not used during read) 42 | #define WRGATE_PIN 9 // IDC 24 (not used during read) 43 | #define TRK0_PIN 10 // IDC 26 44 | #define PROT_PIN 11 // IDC 28 45 | #define READ_PIN 12 // IDC 30 46 | #define SIDE_PIN 13 // IDC 32 47 | #define READY_PIN 14 // IDC 34 48 | #ifndef USE_TINYUSB 49 | #error "Please set Adafruit TinyUSB under Tools > USB Stack" 50 | #endif 51 | #elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040) 52 | // Yay built in pin definitions! 53 | #else 54 | #error "Please set up pin definitions!" 55 | #endif 56 | 57 | Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN, 58 | MOTOR_PIN, DIR_PIN, STEP_PIN, 59 | WRDATA_PIN, WRGATE_PIN, TRK0_PIN, 60 | PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN); 61 | 62 | // WARNING! there are 150K max flux pulses per track! 63 | uint8_t flux_transitions[MAX_FLUX_PULSE_PER_TRACK]; 64 | 65 | uint32_t time_stamp = 0; 66 | 67 | 68 | void setup() { 69 | Serial.begin(115200); 70 | while (!Serial) delay(100); 71 | 72 | #if defined(FLOPPY_DIRECTION_PIN) 73 | pinMode(FLOPPY_DIRECTION_PIN, OUTPUT); 74 | digitalWrite(FLOPPY_DIRECTION_PIN, HIGH); 75 | #endif 76 | #if defined(FLOPPY_ENABLE_PIN) 77 | pinMode(FLOPPY_ENABLE_PIN, OUTPUT); 78 | digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction 79 | #endif 80 | 81 | Serial.println("its time for a nice floppy transfer!"); 82 | Serial.print("Sample freqency "); 83 | Serial.print(floppy.getSampleFrequency() / 1e6); 84 | Serial.println("MHz"); 85 | 86 | floppy.debug_serial = &Serial; 87 | 88 | if (!floppy.begin()) { 89 | Serial.println("Failed to initialize floppy interface"); 90 | while (1) yield(); 91 | } 92 | 93 | floppy.select(true); 94 | if (! floppy.spin_motor(true)) { 95 | Serial.println("Failed to spin up motor & find index pulse"); 96 | while (1) yield(); 97 | } 98 | 99 | Serial.print("Seeking track..."); 100 | if (! floppy.goto_track(0)) { 101 | Serial.println("Failed to seek to track"); 102 | while (1) yield(); 103 | } 104 | Serial.println("done!"); 105 | } 106 | 107 | void loop() { 108 | int32_t index_pulse_offset; 109 | uint32_t captured_flux = floppy.capture_track(flux_transitions, sizeof(flux_transitions), &index_pulse_offset, true); 110 | 111 | Serial.print("Captured "); 112 | Serial.print(captured_flux); 113 | Serial.println(" flux transitions"); 114 | 115 | //floppy.print_pulses(flux_transitions, captured_flux); 116 | floppy.print_pulse_bins(flux_transitions, captured_flux, 255, true); 117 | 118 | if ((millis() - time_stamp) > 1000) { 119 | Serial.print("Ready? "); 120 | Serial.println(digitalRead(READY_PIN) ? "No" : "Yes"); 121 | Serial.print("Write Protected? "); 122 | Serial.println(floppy.get_write_protect() ? "Yes" : "No"); 123 | Serial.print("Track 0? "); 124 | Serial.println(digitalRead(TRK0_PIN) ? "No" : "Yes"); 125 | time_stamp = millis(); 126 | } 127 | yield(); 128 | } 129 | -------------------------------------------------------------------------------- /examples/02_mfm_test/02_mfm_test.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(ADAFRUIT_FEATHER_M4_EXPRESS) 4 | #define DENSITY_PIN A1 // IDC 2 5 | #define INDEX_PIN A5 // IDC 8 6 | #define SELECT_PIN A0 // IDC 12 7 | #define MOTOR_PIN A2 // IDC 16 8 | #define DIR_PIN A3 // IDC 18 9 | #define STEP_PIN A4 // IDC 20 10 | #define WRDATA_PIN 13 // IDC 22 11 | #define WRGATE_PIN 12 // IDC 24 12 | #define TRK0_PIN 10 // IDC 26 13 | #define PROT_PIN 11 // IDC 28 14 | #define READ_PIN 9 // IDC 30 15 | #define SIDE_PIN 6 // IDC 32 16 | #define READY_PIN 5 // IDC 34 17 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) || defined(ARDUINO_ADAFRUIT_FEATHER_RP2350_HSTX) 18 | #define DENSITY_PIN A1 // IDC 2 19 | #define INDEX_PIN 25 // IDC 8 20 | #define SELECT_PIN A0 // IDC 12 21 | #define MOTOR_PIN A2 // IDC 16 22 | #define DIR_PIN A3 // IDC 18 23 | #define STEP_PIN 24 // IDC 20 24 | #define WRDATA_PIN 13 // IDC 22 25 | #define WRGATE_PIN 12 // IDC 24 26 | #define TRK0_PIN 10 // IDC 26 27 | #define PROT_PIN 11 // IDC 28 28 | #define READ_PIN 9 // IDC 30 29 | #define SIDE_PIN 8 // IDC 32 30 | #define READY_PIN 7 // IDC 34 31 | #ifndef USE_TINYUSB 32 | #error "Please set Adafruit TinyUSB under Tools > USB Stack" 33 | #endif 34 | #elif defined(ARDUINO_RASPBERRY_PI_PICO) 35 | #define DENSITY_PIN 2 // IDC 2 36 | #define INDEX_PIN 3 // IDC 8 37 | #define SELECT_PIN 4 // IDC 12 38 | #define MOTOR_PIN 5 // IDC 16 39 | #define DIR_PIN 6 // IDC 18 40 | #define STEP_PIN 7 // IDC 20 41 | #define WRDATA_PIN 8 // IDC 22 (not used during read) 42 | #define WRGATE_PIN 9 // IDC 24 (not used during read) 43 | #define TRK0_PIN 10 // IDC 26 44 | #define PROT_PIN 11 // IDC 28 45 | #define READ_PIN 12 // IDC 30 46 | #define SIDE_PIN 13 // IDC 32 47 | #define READY_PIN 14 // IDC 34 48 | #ifndef USE_TINYUSB 49 | #error "Please set Adafruit TinyUSB under Tools > USB Stack" 50 | #endif 51 | #elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040) 52 | // Yay built in pin definitions! 53 | #else 54 | #error "Please set up pin definitions!" 55 | #endif 56 | 57 | 58 | Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN, 59 | MOTOR_PIN, DIR_PIN, STEP_PIN, 60 | WRDATA_PIN, WRGATE_PIN, TRK0_PIN, 61 | PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN); 62 | 63 | // You can select IBMPC1440K or IBMPC360K (check adafruit_floppy_disk_t options!) 64 | Adafruit_MFM_Floppy mfm_floppy(&floppy, IBMPC1440K); 65 | 66 | 67 | uint32_t time_stamp = 0; 68 | 69 | void setup() { 70 | pinMode(LED_BUILTIN, OUTPUT); 71 | Serial.begin(115200); 72 | while (!Serial) delay(100); 73 | 74 | #if defined(FLOPPY_DIRECTION_PIN) 75 | pinMode(FLOPPY_DIRECTION_PIN, OUTPUT); 76 | digitalWrite(FLOPPY_DIRECTION_PIN, HIGH); 77 | #endif 78 | #if defined(FLOPPY_ENABLE_PIN) 79 | pinMode(FLOPPY_ENABLE_PIN, OUTPUT); 80 | digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction 81 | #endif 82 | 83 | delay(500); // wait for serial to open 84 | Serial.println("its time for a nice floppy transfer!"); 85 | 86 | floppy.debug_serial = &Serial; 87 | 88 | if (! mfm_floppy.begin()) { 89 | Serial.println("Failed to spin up motor & find index pulse"); 90 | while (1) yield(); 91 | } 92 | } 93 | 94 | uint8_t track = 0; 95 | bool head = 0; 96 | void loop() { 97 | int32_t captured_sectors; 98 | 99 | Serial.printf("Seeking track %d head %d\n", track, head); 100 | captured_sectors = mfm_floppy.readTrack(track, head); 101 | if (captured_sectors < 0) { 102 | Serial.println("Failed to seek to track"); 103 | while (1) yield(); 104 | } 105 | 106 | Serial.printf("Captured %d sectors\n", captured_sectors); 107 | 108 | Serial.print("Validity: "); 109 | for (size_t i = 0; i < mfm_floppy.sectors_per_track(); i++) { 110 | Serial.print(mfm_floppy.track_validity[i] ? "V" : "?"); 111 | } 112 | Serial.print("\n"); 113 | for (size_t sector = 0; sector < mfm_floppy.sectors_per_track(); sector++) { 114 | if (!mfm_floppy.track_validity[sector]) { 115 | continue; // skip it, not valid 116 | } 117 | for (size_t i = 0; i < 512; i += 16) { 118 | size_t addr = sector * 512 + i; 119 | Serial.printf("%08x", addr); 120 | for (size_t j = 0; j < 16; j++) { 121 | Serial.printf(" %02x", mfm_floppy.track_data[addr + j]); 122 | } 123 | Serial.print(" | "); 124 | for (size_t j = 0; j < 16; j++) { 125 | uint8_t d = mfm_floppy.track_data[addr + j]; 126 | if (! isprint(d)) { 127 | d = ' '; 128 | } 129 | Serial.write(d); 130 | } 131 | Serial.print("\n"); 132 | } 133 | } 134 | 135 | // advance to next track 136 | if (!head) { // we were on side 0 137 | head = 1; // go to side 1 138 | } else { // we were on side 1? 139 | track = (track + 1) % mfm_floppy.tracks_per_side(); // next track! 140 | head = 0; // and side 0 141 | } 142 | 143 | delay(1000); 144 | } -------------------------------------------------------------------------------- /examples/03_fat_test/03_fat_test.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Print size, modify date/time, and name for all files in root. 3 | */ 4 | 5 | /********************************************************************* 6 | Adafruit invests time and resources providing this open source code, 7 | please support Adafruit and open-source hardware by purchasing 8 | products from Adafruit! 9 | *********************************************************************/ 10 | 11 | #include 12 | #include "SdFat.h" 13 | #include 14 | 15 | #if defined(ADAFRUIT_FEATHER_M4_EXPRESS) 16 | #define DENSITY_PIN A1 // IDC 2 17 | #define INDEX_PIN A5 // IDC 8 18 | #define SELECT_PIN A0 // IDC 12 19 | #define MOTOR_PIN A2 // IDC 16 20 | #define DIR_PIN A3 // IDC 18 21 | #define STEP_PIN A4 // IDC 20 22 | #define WRDATA_PIN 13 // IDC 22 23 | #define WRGATE_PIN 12 // IDC 24 24 | #define TRK0_PIN 10 // IDC 26 25 | #define PROT_PIN 11 // IDC 28 26 | #define READ_PIN 9 // IDC 30 27 | #define SIDE_PIN 6 // IDC 32 28 | #define READY_PIN 5 // IDC 34 29 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) || defined(ARDUINO_ADAFRUIT_FEATHER_RP2350_HSTX) 30 | #define DENSITY_PIN A1 // IDC 2 31 | #define INDEX_PIN 25 // IDC 8 32 | #define SELECT_PIN A0 // IDC 12 33 | #define MOTOR_PIN A2 // IDC 16 34 | #define DIR_PIN A3 // IDC 18 35 | #define STEP_PIN 24 // IDC 20 36 | #define WRDATA_PIN 13 // IDC 22 37 | #define WRGATE_PIN 12 // IDC 24 38 | #define TRK0_PIN 10 // IDC 26 39 | #define PROT_PIN 11 // IDC 28 40 | #define READ_PIN 9 // IDC 30 41 | #define SIDE_PIN 8 // IDC 32 42 | #define READY_PIN 7 // IDC 34 43 | #ifndef USE_TINYUSB 44 | #error "Please set Adafruit TinyUSB under Tools > USB Stack" 45 | #endif 46 | #elif defined(ARDUINO_RASPBERRY_PI_PICO) 47 | #define DENSITY_PIN 2 // IDC 2 48 | #define INDEX_PIN 3 // IDC 8 49 | #define SELECT_PIN 4 // IDC 12 50 | #define MOTOR_PIN 5 // IDC 16 51 | #define DIR_PIN 6 // IDC 18 52 | #define STEP_PIN 7 // IDC 20 53 | #define WRDATA_PIN 8 // IDC 22 (not used during read) 54 | #define WRGATE_PIN 9 // IDC 24 (not used during read) 55 | #define TRK0_PIN 10 // IDC 26 56 | #define PROT_PIN 11 // IDC 28 57 | #define READ_PIN 12 // IDC 30 58 | #define SIDE_PIN 13 // IDC 32 59 | #define READY_PIN 14 // IDC 34 60 | #ifndef USE_TINYUSB 61 | #error "Please set Adafruit TinyUSB under Tools > USB Stack" 62 | #endif 63 | #elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040) 64 | // Yay built in pin definitions! 65 | #else 66 | #error "Please set up pin definitions!" 67 | #endif 68 | 69 | Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN, 70 | MOTOR_PIN, DIR_PIN, STEP_PIN, 71 | WRDATA_PIN, WRGATE_PIN, TRK0_PIN, 72 | PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN); 73 | Adafruit_MFM_Floppy mfm_floppy(&floppy); 74 | 75 | // file system object from SdFat 76 | FatVolume fatfs; 77 | 78 | File32 root; 79 | File32 file; 80 | 81 | //------------------------------------------------------------------------------ 82 | void setup() { 83 | Serial.begin(115200); 84 | 85 | // Wait for USB Serial 86 | while (!Serial) { 87 | yield(); 88 | } 89 | 90 | #if defined(FLOPPY_DIRECTION_PIN) 91 | pinMode(FLOPPY_DIRECTION_PIN, OUTPUT); 92 | digitalWrite(FLOPPY_DIRECTION_PIN, HIGH); 93 | #endif 94 | #if defined(FLOPPY_ENABLE_PIN) 95 | pinMode(FLOPPY_ENABLE_PIN, OUTPUT); 96 | digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction 97 | #endif 98 | 99 | Serial.println("Floppy FAT directory listing demo"); 100 | 101 | // Init floppy drive - must spin up and find index 102 | if (! mfm_floppy.begin()) { 103 | Serial.println("Floppy didn't initialize - check wiring and diskette!"); 104 | while (1) yield(); 105 | } 106 | 107 | // Init file system on the flash 108 | fatfs.begin(&mfm_floppy); 109 | 110 | if (!root.open("/")) { 111 | Serial.println("open root failed"); 112 | while (1) yield(); 113 | } 114 | 115 | // Open next file in root. 116 | // Warning, openNext starts at the current directory position 117 | // so a rewind of the directory may be required. 118 | while (file.openNext(&root, O_RDONLY)) { 119 | file.printFileSize(&Serial); 120 | Serial.write(' '); 121 | file.printModifyDateTime(&Serial); 122 | Serial.write(' '); 123 | file.printName(&Serial); 124 | if (file.isDir()) { 125 | // Indicate a directory. 126 | Serial.write('/'); 127 | } 128 | Serial.println(); 129 | file.close(); 130 | } 131 | 132 | if (root.getError()) { 133 | Serial.println("openNext failed"); 134 | } else { 135 | Serial.println("Done!"); 136 | } 137 | } 138 | //------------------------------------------------------------------------------ 139 | void loop() { 140 | Serial.print("\n\nRead a file? >"); 141 | Serial.flush(); 142 | delay(10); 143 | 144 | String filename; 145 | do { 146 | filename = Serial.readStringUntil('\n'); 147 | filename.trim(); 148 | } while (filename.length() == 0); 149 | 150 | Serial.print("Reading file name: "); 151 | Serial.println(filename); 152 | 153 | // Open the file for reading and check that it was successfully opened. 154 | // The FILE_READ mode will open the file for reading. 155 | File32 dataFile = fatfs.open(filename, FILE_READ); 156 | if (!dataFile) { 157 | Serial.println("Failed to open data file! Does it exist?"); 158 | return; 159 | } 160 | // File was opened, now print out data character by character until at the 161 | // end of the file. 162 | Serial.println("Opened file, printing contents below:"); 163 | while (dataFile.available()) { 164 | // Use the read function to read the next character. 165 | // You can alternatively use other functions like readUntil, readString, etc. 166 | // See the fatfs_full_usage example for more details. 167 | char c = dataFile.read(); 168 | Serial.print(c); 169 | } 170 | } -------------------------------------------------------------------------------- /examples/04_msd_test/.feather_m4_express_tinyusb.generate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_Floppy/7c489bf56b629d5f01bdf65a83cd6c5f7a098b79/examples/04_msd_test/.feather_m4_express_tinyusb.generate -------------------------------------------------------------------------------- /examples/04_msd_test/.feather_rp2040_tinyusb.generate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_Floppy/7c489bf56b629d5f01bdf65a83cd6c5f7a098b79/examples/04_msd_test/.feather_rp2040_tinyusb.generate -------------------------------------------------------------------------------- /examples/04_msd_test/.feather_rp2350_tinyusb.generate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_Floppy/7c489bf56b629d5f01bdf65a83cd6c5f7a098b79/examples/04_msd_test/.feather_rp2350_tinyusb.generate -------------------------------------------------------------------------------- /examples/04_msd_test/.floppsy_rp2040_tinyusb.generate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_Floppy/7c489bf56b629d5f01bdf65a83cd6c5f7a098b79/examples/04_msd_test/.floppsy_rp2040_tinyusb.generate -------------------------------------------------------------------------------- /examples/04_msd_test/04_msd_test.ino: -------------------------------------------------------------------------------- 1 | // this example makes a lot of assumptions: MFM floppy which is already inserted 2 | // and only reading is supported - no write yet! 3 | 4 | #include "Adafruit_TinyUSB.h" 5 | #include 6 | 7 | #if defined(ADAFRUIT_FEATHER_M4_EXPRESS) 8 | #define DENSITY_PIN A1 // IDC 2 9 | #define INDEX_PIN A5 // IDC 8 10 | #define SELECT_PIN A0 // IDC 12 11 | #define MOTOR_PIN A2 // IDC 16 12 | #define DIR_PIN A3 // IDC 18 13 | #define STEP_PIN A4 // IDC 20 14 | #define WRDATA_PIN 13 // IDC 22 15 | #define WRGATE_PIN 12 // IDC 24 16 | #define TRK0_PIN 10 // IDC 26 17 | #define PROT_PIN 11 // IDC 28 18 | #define READ_PIN 9 // IDC 30 19 | #define SIDE_PIN 6 // IDC 32 20 | #define READY_PIN 5 // IDC 34 21 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) || defined(ARDUINO_ADAFRUIT_FEATHER_RP2350_HSTX) 22 | #define DENSITY_PIN A1 // IDC 2 23 | #define INDEX_PIN 25 // IDC 8 24 | #define SELECT_PIN A0 // IDC 12 25 | #define MOTOR_PIN A2 // IDC 16 26 | #define DIR_PIN A3 // IDC 18 27 | #define STEP_PIN 24 // IDC 20 28 | #define WRDATA_PIN 13 // IDC 22 29 | #define WRGATE_PIN 12 // IDC 24 30 | #define TRK0_PIN 10 // IDC 26 31 | #define PROT_PIN 11 // IDC 28 32 | #define READ_PIN 9 // IDC 30 33 | #define SIDE_PIN 8 // IDC 32 34 | #define READY_PIN 7 // IDC 34 35 | #elif defined(ARDUINO_RASPBERRY_PI_PICO) 36 | #define DENSITY_PIN 2 // IDC 2 37 | #define INDEX_PIN 3 // IDC 8 38 | #define SELECT_PIN 4 // IDC 12 39 | #define MOTOR_PIN 5 // IDC 16 40 | #define DIR_PIN 6 // IDC 18 41 | #define STEP_PIN 7 // IDC 20 42 | #define WRDATA_PIN 8 // IDC 22 (not used during read) 43 | #define WRGATE_PIN 9 // IDC 24 (not used during read) 44 | #define TRK0_PIN 10 // IDC 26 45 | #define PROT_PIN 11 // IDC 28 46 | #define READ_PIN 12 // IDC 30 47 | #define SIDE_PIN 13 // IDC 32 48 | #define READY_PIN 14 // IDC 34 49 | #elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040) 50 | // Yay built in pin definitions! 51 | // enable the display, though! 52 | #include "display_floppsy.h" 53 | #else 54 | #error "Please set up pin definitions!" 55 | #endif 56 | 57 | #ifndef USE_TINYUSB 58 | #error "Please set Adafruit TinyUSB under Tools > USB Stack" 59 | #endif 60 | 61 | Adafruit_USBD_MSC usb_msc; 62 | 63 | Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN, MOTOR_PIN, DIR_PIN, 64 | STEP_PIN, WRDATA_PIN, WRGATE_PIN, TRK0_PIN, PROT_PIN, 65 | READ_PIN, SIDE_PIN, READY_PIN); 66 | 67 | // You can select IBMPC1440K or IBMPC360K (check adafruit_floppy_disk_t 68 | // options!) 69 | auto FLOPPY_TYPE = AUTODETECT; 70 | Adafruit_MFM_Floppy mfm_floppy(&floppy, FLOPPY_TYPE); 71 | 72 | // To make a display on another board, check out "display_floppsy.h"; adapt it 73 | // to your board & include it 74 | #if defined(HAVE_DISPLAY) 75 | #include "display_common.h" 76 | #else 77 | #include "display_none.h" 78 | #endif 79 | 80 | constexpr size_t SECTOR_SIZE = 512UL; 81 | 82 | void setup() { 83 | Serial.begin(115200); 84 | 85 | #if defined(FLOPPY_DIRECTION_PIN) 86 | pinMode(FLOPPY_DIRECTION_PIN, OUTPUT); 87 | digitalWrite(FLOPPY_DIRECTION_PIN, HIGH); 88 | #endif 89 | #if defined(FLOPPY_ENABLE_PIN) 90 | pinMode(FLOPPY_ENABLE_PIN, OUTPUT); 91 | digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction 92 | #endif 93 | 94 | #if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040) 95 | // Manual begin() is required on core without built-in support for TinyUSB 96 | // such as 97 | // - mbed rp2040 98 | TinyUSB_Device_Init(0); 99 | #endif 100 | 101 | // Set disk vendor id, product id and revision with string up to 8, 16, 4 102 | // characters respectively 103 | usb_msc.setID("Adafruit", "Floppy Mass Storage", "1.0"); 104 | 105 | // Set disk size 106 | usb_msc.setCapacity(0, SECTOR_SIZE); 107 | // Set callbacks 108 | usb_msc.setReadyCallback(0, msc_ready_callback); 109 | usb_msc.setWritableCallback(0, msc_writable_callback); 110 | usb_msc.setReadWriteCallback(msc_read_callback, msc_write_callback, 111 | msc_flush_callback); 112 | 113 | // floppy.debug_serial = &Serial; 114 | // Set Lun ready 115 | usb_msc.setUnitReady(false); 116 | Serial.println("Ready!"); 117 | usb_msc.begin(); 118 | 119 | Serial.println("serial Ready!"); 120 | 121 | init_display(); 122 | 123 | floppy.begin(); 124 | attachInterrupt(digitalPinToInterrupt(INDEX_PIN), count_index, FALLING); 125 | if (mfm_floppy.begin()) { 126 | mfm_floppy.inserted(FLOPPY_TYPE); 127 | } 128 | } 129 | 130 | volatile uint32_t flush_time; 131 | 132 | volatile uint32_t index_count; 133 | volatile uint32_t index_time, last_index_time; 134 | void count_index() { 135 | index_count += 1; 136 | last_index_time = index_time; 137 | index_time = millis(); 138 | } 139 | 140 | bool index_delayed, ready_delayed; 141 | uint32_t old_index_count; 142 | void loop() { 143 | noInterrupts(); 144 | uint32_t now = millis(); 145 | auto index = !digitalRead(INDEX_PIN); 146 | auto ready = digitalRead(READY_PIN); 147 | auto new_index_count = index_count; 148 | auto new_index_time = index_time; 149 | auto time_since_index = now - new_index_time; 150 | interrupts(); 151 | 152 | if (mfm_floppy.dirty() && now > flush_time) { 153 | noInterrupts(); 154 | mfm_floppy.syncDevice(); 155 | interrupts(); 156 | } 157 | 158 | // ready pin fell or no index for 400ms: media removed 159 | // (the check for nonzero index count is an attempt to future-proof against 160 | // a no-index 3.5" drive) 161 | bool removed = 162 | (!ready && ready_delayed) || (index_count && time_since_index > 1200); 163 | 164 | if (removed) { 165 | if (mfm_floppy.sectorCount() != 0) { 166 | Serial.println("removed"); 167 | mfm_floppy.removed(); 168 | } 169 | } 170 | if (new_index_count != old_index_count) { 171 | if (mfm_floppy.sectorCount() == 0) { 172 | Serial.println("inserted"); 173 | mfm_floppy.inserted(FLOPPY_TYPE); 174 | } 175 | } 176 | 177 | maybe_update_display(false, new_index_count != old_index_count); 178 | ready_delayed = ready; 179 | old_index_count = new_index_count; 180 | } 181 | 182 | // Callback invoked when received READ10 command. 183 | // Copy disk's data to buffer (up to bufsize) and 184 | // return number of copied bytes (must be multiple of block size) 185 | int32_t msc_read_callback(uint32_t lba, void *buffer, uint32_t bufsize) { 186 | // Serial.printf("read call back block %d size %d\r\n", lba, bufsize); 187 | auto result = mfm_floppy.readSectors(lba, reinterpret_cast(buffer), 188 | bufsize / MFM_BYTES_PER_SECTOR); 189 | return result ? bufsize : -1; 190 | } 191 | 192 | // Callback invoked when received WRITE10 command. 193 | // Process data in buffer to disk's storage and 194 | // return number of written bytes (must be multiple of block size) 195 | int32_t msc_write_callback(uint32_t lba, uint8_t *buffer, uint32_t bufsize) { 196 | // Serial.printf("write call back block %d size %d\r\n", lba, bufsize); 197 | auto sectors = bufsize / MFM_BYTES_PER_SECTOR; 198 | auto result = mfm_floppy.writeSectors(lba, buffer, sectors); 199 | if (result) { 200 | flush_time = millis() + 200; 201 | if (lba == 0 || (lba + sectors) == mfm_floppy.sectorCount()) { 202 | // If writing the first or last sector, 203 | mfm_floppy.syncDevice(); 204 | } 205 | } 206 | return result ? bufsize : -1; 207 | } 208 | 209 | // Callback invoked when WRITE10 command is completed (status received and 210 | // accepted by host). used to flush any pending cache. 211 | void msc_flush_callback(void) { 212 | Serial.print("flush\r\n"); 213 | mfm_floppy.syncDevice(); 214 | // nothing to do 215 | } 216 | 217 | bool msc_ready_callback(void) { 218 | // Serial.printf("ready callback -> %d\r\n", mfm_floppy.sectorCount()); 219 | auto sectors = mfm_floppy.sectorCount(); 220 | usb_msc.setCapacity(sectors, SECTOR_SIZE); 221 | return sectors != 0; 222 | } 223 | 224 | bool msc_writable_callback(void) { return !floppy.get_write_protect(); } 225 | -------------------------------------------------------------------------------- /examples/04_msd_test/display_common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | bool ever_refreshed; 4 | 5 | void update_display(bool force_refresh); 6 | 7 | bool operator!=(const display_state &a, const display_state &b) { 8 | return a.capacity_kib != b.capacity_kib || a.trk0 != b.trk0 || a.wp != b.wp || 9 | a.rdy != b.rdy || a.dirty != b.dirty || a.trk != b.trk || 10 | a.side != b.side; 11 | } 12 | 13 | void maybe_update_display(bool force_refresh, bool tick) { 14 | noInterrupts(); 15 | new_state = display_state{ 16 | mfm_floppy.sectorCount() / 2, 17 | floppy.get_track0_sense(), 18 | floppy.get_write_protect(), 19 | !!digitalRead(READY_PIN), 20 | mfm_floppy.dirty(), 21 | floppy.track(), 22 | floppy.get_side(), 23 | }; 24 | interrupts(); 25 | 26 | force_refresh = force_refresh || !ever_refreshed; 27 | if (force_refresh || (old_state != new_state) || tick) { 28 | update_display(force_refresh); 29 | old_state = new_state; 30 | ever_refreshed = true; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/04_msd_test/display_floppsy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "display_state.h" 3 | #include // Hardware-specific library for ST7789 4 | #define HAVE_DISPLAY (1) 5 | 6 | Adafruit_ST7789 display = Adafruit_ST7789(&SPI1, TFT_CS, TFT_DC, TFT_RESET); 7 | 8 | enum { SZ = 3 }; 9 | 10 | static void setCursor(int x, int y) { 11 | display.setCursor(x * SZ * 6 + 12, y * SZ * 8 + 12); 12 | } 13 | 14 | void init_display() { 15 | display.init(240, 240); 16 | display.fillScreen(0); 17 | pinMode(TFT_BACKLIGHT, OUTPUT); 18 | digitalWrite(TFT_BACKLIGHT, 1); 19 | display.setTextSize(SZ); 20 | } 21 | 22 | /*! 23 | @brief Convert hue, saturation and value into a packed 16-bit RGB color 24 | that can be passed to TFT 25 | @param H The Hue ranging from 0 to 359 26 | @param S Saturation, 8-bit value, 0 (min or pure grayscale) to 100 27 | (max or pure hue) 28 | @param V Value (brightness), 8-bit value, 0 (min / black / off) to 29 | 100 (max or full brightness) 30 | @return Packed 16-bit 5-6-5 RGB. Result is linearly but not perceptually 31 | correct for LEDs. Intended for TFT use only. 32 | */ 33 | // https://gist.github.com/kuathadianto/200148f53616cbd226d993b400214a7f 34 | uint16_t ColorHSV565(int16_t H, uint8_t S = 100, uint8_t V = 100) { 35 | float C = S * V / 10000.0f; 36 | float X = C * (1 - abs(fmod(H / 60.0f, 2) - 1)); 37 | float m = (V / 100.0f) - C; 38 | float Rs, Gs, Bs; 39 | 40 | if (H >= 0 && H < 60) { 41 | Rs = C; 42 | Gs = X; 43 | Bs = 0; 44 | } else if (H >= 60 && H < 120) { 45 | Rs = X; 46 | Gs = C; 47 | Bs = 0; 48 | } else if (H >= 120 && H < 180) { 49 | Rs = 0; 50 | Gs = C; 51 | Bs = X; 52 | } else if (H >= 180 && H < 240) { 53 | Rs = 0; 54 | Gs = X; 55 | Bs = C; 56 | } else if (H >= 240 && H < 300) { 57 | Rs = X; 58 | Gs = 0; 59 | Bs = C; 60 | } else { 61 | Rs = C; 62 | Gs = 0; 63 | Bs = X; 64 | } 65 | 66 | uint8_t red = (Rs + m) * 255; 67 | uint8_t green = (Gs + m) * 255; 68 | uint8_t blue = (Bs + m) * 255; 69 | return display.color565(red, green, blue); 70 | } 71 | 72 | void update_display(bool force_refresh) { 73 | int x = 3; 74 | int y = 3; 75 | 76 | if (force_refresh) { 77 | display.fillScreen(0); 78 | } 79 | 80 | static int phase = 0; 81 | phase = phase + 53; 82 | 83 | // Top row 84 | int row = 0; 85 | setCursor(2, 0); 86 | for (int i = 0; i < 7; i++) { 87 | display.setTextColor(ColorHSV565((i * 360 / 7 + phase) % 360), 0); 88 | display.print("FLOPPSY"[i]); 89 | } 90 | 91 | // Media row 92 | row += 2; 93 | if (force_refresh || new_state.capacity_kib != old_state.capacity_kib) { 94 | setCursor(0, row); 95 | Serial.printf("row 2 dirty capacity_kib=%d\n", new_state.capacity_kib); 96 | if (new_state.capacity_kib) { 97 | display.setTextColor(ST77XX_WHITE, 0); 98 | display.printf("%d KiB ", new_state.capacity_kib); 99 | } else { 100 | display.setTextColor(ST77XX_MAGENTA, 0); 101 | display.printf("NO MEDIA"); 102 | } 103 | } 104 | 105 | // Head position row 106 | row += 3; 107 | printf("new trk=%d old_trk=%d\n", new_state.trk, old_state.trk); 108 | if (force_refresh || new_state.trk != old_state.trk) { 109 | if (force_refresh) { 110 | setCursor(0, row); 111 | display.setTextColor(ST77XX_WHITE, 0); 112 | display.print("T:"); 113 | } 114 | setCursor(2, row); 115 | if (new_state.trk < 0 || new_state.trk > 99) { 116 | display.setTextColor(ST77XX_RED, 0); 117 | display.print("??"); 118 | } else { 119 | display.setTextColor(ST77XX_GREEN, 0); 120 | display.printf("%02d", new_state.trk); 121 | } 122 | } 123 | if (force_refresh || new_state.side != old_state.side) { 124 | display.setTextColor(ST77XX_WHITE, 0); 125 | if (force_refresh) { 126 | setCursor(5, row); 127 | display.print("S:"); 128 | } 129 | setCursor(7, row); 130 | display.printf("%d", new_state.side); 131 | } 132 | 133 | // Dirty row 134 | row += 2; 135 | if (force_refresh || new_state.dirty != old_state.dirty) { 136 | display.setTextColor(ST77XX_MAGENTA, 0); 137 | setCursor(0, row); 138 | display.print(new_state.dirty ? "dirty" : " "); 139 | } 140 | 141 | // Sense row 142 | row += 1; 143 | if (force_refresh || new_state.trk0 != old_state.trk0) { 144 | setCursor(0, row); 145 | display.setTextColor(ST77XX_GREEN, 0); 146 | display.print(new_state.trk0 ? "TRK0" : " "); 147 | }; 148 | 149 | if (force_refresh || new_state.wp != old_state.wp) { 150 | setCursor(5, row); 151 | display.setTextColor(ST77XX_MAGENTA, 0); 152 | display.print(new_state.wp ? "R/O" : " "); 153 | }; 154 | 155 | if (force_refresh || new_state.rdy != old_state.rdy) { 156 | setCursor(9, row); 157 | display.setTextColor(ST77XX_CYAN, 0); 158 | display.print(new_state.rdy ? "RDY" : " "); 159 | }; 160 | } 161 | -------------------------------------------------------------------------------- /examples/04_msd_test/display_none.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void init_display() {} 4 | void maybe_update_display(bool, bool) {} 5 | -------------------------------------------------------------------------------- /examples/04_msd_test/display_state.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct display_state { 4 | size_t capacity_kib; 5 | bool trk0, wp, rdy, dirty; 6 | int8_t trk, side; 7 | }; 8 | 9 | display_state old_state, new_state; 10 | -------------------------------------------------------------------------------- /examples/05_mfm_write_test/05_mfm_write_test.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(ADAFRUIT_FEATHER_M4_EXPRESS) 4 | #define DENSITY_PIN A1 // IDC 2 5 | #define INDEX_PIN A5 // IDC 8 6 | #define SELECT_PIN A0 // IDC 12 7 | #define MOTOR_PIN A2 // IDC 16 8 | #define DIR_PIN A3 // IDC 18 9 | #define STEP_PIN A4 // IDC 20 10 | #define WRDATA_PIN 13 // IDC 22 11 | #define WRGATE_PIN 12 // IDC 24 12 | #define TRK0_PIN 10 // IDC 26 13 | #define PROT_PIN 11 // IDC 28 14 | #define READ_PIN 9 // IDC 30 15 | #define SIDE_PIN 6 // IDC 32 16 | #define READY_PIN 5 // IDC 34 17 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) || defined(ARDUINO_ADAFRUIT_FEATHER_RP2350_HSTX) 18 | #define DENSITY_PIN A1 // IDC 2 19 | #define INDEX_PIN 25 // IDC 8 20 | #define SELECT_PIN A0 // IDC 12 21 | #define MOTOR_PIN A2 // IDC 16 22 | #define DIR_PIN A3 // IDC 18 23 | #define STEP_PIN 24 // IDC 20 24 | #define WRDATA_PIN 13 // IDC 22 25 | #define WRGATE_PIN 12 // IDC 24 26 | #define TRK0_PIN 10 // IDC 26 27 | #define PROT_PIN 11 // IDC 28 28 | #define READ_PIN 9 // IDC 30 29 | #define SIDE_PIN 8 // IDC 32 30 | #define READY_PIN 7 // IDC 34 31 | #ifndef USE_TINYUSB 32 | #error "Please set Adafruit TinyUSB under Tools > USB Stack" 33 | #endif 34 | #elif defined(ARDUINO_RASPBERRY_PI_PICO) 35 | #define DENSITY_PIN 2 // IDC 2 36 | #define INDEX_PIN 3 // IDC 8 37 | #define SELECT_PIN 4 // IDC 12 38 | #define MOTOR_PIN 5 // IDC 16 39 | #define DIR_PIN 6 // IDC 18 40 | #define STEP_PIN 7 // IDC 20 41 | #define WRDATA_PIN 8 // IDC 22 (not used during read) 42 | #define WRGATE_PIN 9 // IDC 24 (not used during read) 43 | #define TRK0_PIN 10 // IDC 26 44 | #define PROT_PIN 11 // IDC 28 45 | #define READ_PIN 12 // IDC 30 46 | #define SIDE_PIN 13 // IDC 32 47 | #define READY_PIN 14 // IDC 34 48 | #ifndef USE_TINYUSB 49 | #error "Please set Adafruit TinyUSB under Tools > USB Stack" 50 | #endif 51 | #elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040) 52 | // Yay built in pin definitions! 53 | #else 54 | #error "Please set up pin definitions!" 55 | #endif 56 | 57 | 58 | Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN, 59 | MOTOR_PIN, DIR_PIN, STEP_PIN, 60 | WRDATA_PIN, WRGATE_PIN, TRK0_PIN, 61 | PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN); 62 | 63 | // You can select IBMPC1440K or IBMPC360K (check adafruit_floppy_disk_t options!) 64 | Adafruit_MFM_Floppy mfm_floppy(&floppy, IBMPC1440K); 65 | 66 | 67 | uint32_t time_stamp = 0; 68 | 69 | void setup() { 70 | pinMode(LED_BUILTIN, OUTPUT); 71 | Serial.begin(115200); 72 | while (!Serial) delay(100); 73 | 74 | #if defined(FLOPPY_DIRECTION_PIN) 75 | pinMode(FLOPPY_DIRECTION_PIN, OUTPUT); 76 | digitalWrite(FLOPPY_DIRECTION_PIN, HIGH); 77 | #endif 78 | #if defined(FLOPPY_ENABLE_PIN) 79 | pinMode(FLOPPY_ENABLE_PIN, OUTPUT); 80 | digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction 81 | #endif 82 | 83 | delay(500); // wait for serial to open 84 | Serial.println("its time for a nice floppy transfer!"); 85 | 86 | floppy.debug_serial = &Serial; 87 | 88 | if (! mfm_floppy.begin()) { 89 | Serial.println("Failed to spin up motor & find index pulse"); 90 | while (1) yield(); 91 | } 92 | } 93 | 94 | void hexdump(size_t offset, const uint8_t *data, size_t n) { 95 | for (size_t i = 0; i < n; i += 16) { 96 | size_t addr = offset + i; 97 | Serial.printf("%08x", addr); 98 | for (size_t j = 0; j < 16; j++) { 99 | if(i+j > n) Serial.printf(" ");else 100 | Serial.printf(" %02x", mfm_floppy.track_data[addr + j]); 101 | } 102 | Serial.print(" | "); 103 | for (size_t j = 0; j < 16; j++) { 104 | if(i+j > n) break; 105 | uint8_t d = mfm_floppy.track_data[addr + j]; 106 | if (! isprint(d)) { 107 | d = ' '; 108 | } 109 | Serial.write(d); 110 | } 111 | Serial.print("\n"); 112 | } 113 | } 114 | 115 | uint8_t track = 0; 116 | bool head = 0; 117 | int i = 0; 118 | void loop() { 119 | int32_t captured_sectors; 120 | 121 | uint8_t sector[512]; 122 | int lba = (i++ % 2 == 0) ? 0 : 18; 123 | if (!mfm_floppy.readSector(lba, sector)) { 124 | Serial.println("Failed to read sector"); 125 | return; 126 | } 127 | 128 | hexdump(lba * 512, sector, 512); 129 | 130 | memset(sector, 0, 512); 131 | snprintf(reinterpret_cast(sector), sizeof(sector), "Hello from iteration %zd of Adafruit Floppy MFM writing\n", i); 132 | 133 | if (!mfm_floppy.writeSector(lba, sector)) { 134 | Serial.println("Failed to write sectorn"); 135 | return; 136 | } 137 | if (!mfm_floppy.syncDevice()) { 138 | Serial.println("Failed to sync device"); 139 | return; 140 | } 141 | 142 | delay(1000); 143 | } 144 | -------------------------------------------------------------------------------- /examples/99_floppy_write_test/99_floppy_write_test.ino: -------------------------------------------------------------------------------- 1 | // This example will ERASE TRACK 0 on a floppy disk to test if flux writing 2 | // is functioning 3 | // DO NOT RUN IT ON A FLOPPY YOU WISH TO KEEP DATA! 4 | // IT MUST BE REFORMATTED AFTER WRITING! 5 | 6 | #include 7 | 8 | #if defined(ADAFRUIT_FEATHER_M4_EXPRESS) 9 | #define DENSITY_PIN A1 // IDC 2 10 | #define INDEX_PIN A5 // IDC 8 11 | #define SELECT_PIN A0 // IDC 12 12 | #define MOTOR_PIN A2 // IDC 16 13 | #define DIR_PIN A3 // IDC 18 14 | #define STEP_PIN A4 // IDC 20 15 | #define WRDATA_PIN 13 // IDC 22 16 | #define WRGATE_PIN 12 // IDC 24 17 | #define TRK0_PIN 10 // IDC 26 18 | #define PROT_PIN 11 // IDC 28 19 | #define READ_PIN 9 // IDC 30 20 | #define SIDE_PIN 6 // IDC 32 21 | #define READY_PIN 5 // IDC 34 22 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) || defined(ARDUINO_ADAFRUIT_FEATHER_RP2350_HSTX) 23 | #define DENSITY_PIN A1 // IDC 2 24 | #define INDEX_PIN 25 // IDC 8 25 | #define SELECT_PIN A0 // IDC 12 26 | #define MOTOR_PIN A2 // IDC 16 27 | #define DIR_PIN A3 // IDC 18 28 | #define STEP_PIN 24 // IDC 20 29 | #define WRDATA_PIN 13 // IDC 22 30 | #define WRGATE_PIN 12 // IDC 24 31 | #define TRK0_PIN 10 // IDC 26 32 | #define PROT_PIN 11 // IDC 28 33 | #define READ_PIN 9 // IDC 30 34 | #define SIDE_PIN 8 // IDC 32 35 | #define READY_PIN 7 // IDC 34 36 | #elif defined(ARDUINO_RASPBERRY_PI_PICO) 37 | #define DENSITY_PIN 2 // IDC 2 38 | #define INDEX_PIN 3 // IDC 8 39 | #define SELECT_PIN 4 // IDC 12 40 | #define MOTOR_PIN 5 // IDC 16 41 | #define DIR_PIN 6 // IDC 18 42 | #define STEP_PIN 7 // IDC 20 43 | #define WRDATA_PIN 8 // IDC 22 (not used during read) 44 | #define WRGATE_PIN 9 // IDC 24 (not used during read) 45 | #define TRK0_PIN 10 // IDC 26 46 | #define PROT_PIN 11 // IDC 28 47 | #define READ_PIN 12 // IDC 30 48 | #define SIDE_PIN 13 // IDC 32 49 | #define READY_PIN 14 // IDC 34 50 | #elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040) 51 | // Yay built in pin definitions! 52 | #else 53 | #error "Please set up pin definitions!" 54 | #endif 55 | 56 | #ifndef USE_TINYUSB 57 | #error "Please set Adafruit TinyUSB under Tools > USB Stack" 58 | #endif 59 | 60 | 61 | Adafruit_Floppy floppy(DENSITY_PIN, INDEX_PIN, SELECT_PIN, 62 | MOTOR_PIN, DIR_PIN, STEP_PIN, 63 | WRDATA_PIN, WRGATE_PIN, TRK0_PIN, 64 | PROT_PIN, READ_PIN, SIDE_PIN, READY_PIN); 65 | 66 | // WARNING! there are 150K max flux pulses per track! 67 | uint8_t flux_transitions[MAX_FLUX_PULSE_PER_TRACK]; 68 | 69 | uint32_t time_stamp = 0; 70 | 71 | 72 | void setup() { 73 | Serial.begin(115200); 74 | while (!Serial) delay(100); 75 | 76 | #if defined(FLOPPY_DIRECTION_PIN) 77 | pinMode(FLOPPY_DIRECTION_PIN, OUTPUT); 78 | digitalWrite(FLOPPY_DIRECTION_PIN, HIGH); 79 | #endif 80 | #if defined(FLOPPY_ENABLE_PIN) 81 | pinMode(FLOPPY_ENABLE_PIN, OUTPUT); 82 | digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction 83 | #endif 84 | 85 | Serial.println("its time for a nice floppy transfer!"); 86 | floppy.debug_serial = &Serial; 87 | 88 | if (!floppy.begin()) { 89 | Serial.println("Failed to initialize floppy interface"); 90 | while (1) yield(); 91 | } 92 | 93 | floppy.select(true); 94 | if (! floppy.spin_motor(true)) { 95 | Serial.println("Failed to spin up motor & find index pulse"); 96 | while (1) yield(); 97 | } 98 | 99 | Serial.print("Seeking track..."); 100 | if (! floppy.goto_track(0)) { 101 | Serial.println("Failed to seek to track"); 102 | while (1) yield(); 103 | } 104 | Serial.println("done!"); 105 | 106 | } 107 | 108 | void loop() { 109 | // Flush input of serial port 110 | while (Serial.available()) Serial.read(); 111 | 112 | // Warn them again! 113 | Serial.println("Are you SURE you want to run the write test?"); 114 | Serial.println("THIS WILL PERMANENTLY ERASE ANY DATA ON THE FLOPPY DISK!!!"); 115 | Serial.println("Type Y to continue..."); 116 | while (! Serial.available()) yield(); 117 | if (Serial.read() != 'Y') return; 118 | 119 | int32_t index_pulse_offset; 120 | uint32_t captured_flux = floppy.capture_track(flux_transitions, sizeof(flux_transitions), &index_pulse_offset, true); 121 | 122 | Serial.print("Captured "); 123 | Serial.print(captured_flux); 124 | Serial.println(" flux transitions"); 125 | 126 | //floppy.print_pulses(flux_transitions, captured_flux); 127 | floppy.print_pulse_bins(flux_transitions, captured_flux, 255, true); 128 | 129 | if ((millis() - time_stamp) > 1000) { 130 | Serial.print("Ready? "); 131 | Serial.println(digitalRead(READY_PIN) ? "No" : "Yes"); 132 | Serial.print("Write Protected? "); 133 | Serial.println(digitalRead(PROT_PIN) ? "No" : "Yes"); 134 | Serial.print("Track 0? "); 135 | Serial.println(digitalRead(TRK0_PIN) ? "No" : "Yes"); 136 | time_stamp = millis(); 137 | } 138 | 139 | unsigned T_2 = floppy.getSampleFrequency() * 2 / 1000000; 140 | unsigned T_3 = floppy.getSampleFrequency() * 3 / 1000000; 141 | unsigned T_4 = floppy.getSampleFrequency() * 4 / 1000000; 142 | 143 | for (size_t i = 0; i < sizeof(flux_transitions); i += 3) { 144 | flux_transitions[i] = T_2; 145 | } 146 | for (size_t i = 1; i < sizeof(flux_transitions); i += 3) { 147 | flux_transitions[i] = T_3; 148 | } 149 | for (size_t i = 2; i < sizeof(flux_transitions); i += 3) { 150 | flux_transitions[i] = T_4; 151 | } 152 | 153 | floppy.print_pulse_bins(flux_transitions, captured_flux, 255, true); 154 | 155 | Serial.println("Writing track with T234234..."); 156 | Serial.printf("T2 = %d T3 = %d T4 = %d\n", T_2, T_3, T_4); 157 | floppy.write_track(flux_transitions, sizeof(flux_transitions), true); 158 | 159 | yield(); 160 | } -------------------------------------------------------------------------------- /examples/apple2_test/apple2_test.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(ADAFRUIT_FEATHER_M4_EXPRESS) 4 | #define APPLE2_ENABLE_PIN (6) 5 | #define APPLE2_PHASE1_PIN (A2) 6 | #define APPLE2_PHASE2_PIN (13) 7 | #define APPLE2_PHASE3_PIN (12) 8 | #define APPLE2_PHASE4_PIN (11) 9 | #define APPLE2_RDDATA_PIN (5) 10 | #define APPLE2_INDEX_PIN (A3) 11 | #define APPLE2_PROTECT_PIN (21) // "SDA" 12 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) || defined(ARDUINO_ADAFRUIT_FEATHER_RP2350_HSTX) 13 | #define APPLE2_ENABLE_PIN (8) // D6 14 | #define APPLE2_PHASE1_PIN (A2) 15 | #define APPLE2_PHASE2_PIN (13) 16 | #define APPLE2_PHASE3_PIN (12) 17 | #define APPLE2_PHASE4_PIN (11) 18 | #define APPLE2_RDDATA_PIN (7) // D5 19 | #define APPLE2_INDEX_PIN (A3) 20 | #define APPLE2_PROTECT_PIN (2) // "SDA" 21 | #elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040) 22 | // Yay built in pin definitions! 23 | #else 24 | #error "Please set up pin definitions!" 25 | #endif 26 | 27 | Adafruit_Apple2Floppy floppy(APPLE2_INDEX_PIN, APPLE2_ENABLE_PIN, 28 | APPLE2_PHASE1_PIN, APPLE2_PHASE2_PIN, APPLE2_PHASE3_PIN, APPLE2_PHASE4_PIN, 29 | -1, -1, APPLE2_PROTECT_PIN, APPLE2_RDDATA_PIN); 30 | 31 | // WARNING! there are 150K max flux pulses per track! 32 | uint8_t flux_transitions[MAX_FLUX_PULSE_PER_TRACK]; 33 | 34 | uint32_t time_stamp = 0; 35 | 36 | 37 | void setup() { 38 | Serial.begin(115200); 39 | while (!Serial) delay(100); 40 | 41 | Serial.println("its time for a nice floppy transfer!"); 42 | floppy.debug_serial = &Serial; 43 | 44 | if (!floppy.begin()) { 45 | Serial.println("Failed to initialize floppy interface"); 46 | while (1) yield(); 47 | } 48 | 49 | floppy.select(true); 50 | if (! floppy.spin_motor(true)) { 51 | Serial.println("Failed to spin up motor & find index pulse"); 52 | while (1) yield(); 53 | } 54 | 55 | Serial.print("Seeking track..."); 56 | if (! floppy.goto_track(0)) { 57 | Serial.println("Failed to seek to track"); 58 | while (1) yield(); 59 | } 60 | Serial.println("done!"); 61 | 62 | } 63 | 64 | void loop() { 65 | int32_t index_pulse_offset; 66 | uint32_t captured_flux = floppy.capture_track(flux_transitions, sizeof(flux_transitions), &index_pulse_offset, true); 67 | 68 | Serial.print("Captured "); 69 | Serial.print(captured_flux); 70 | Serial.println(" flux transitions"); 71 | 72 | //floppy.print_pulses(flux_transitions, captured_flux); 73 | floppy.print_pulse_bins(flux_transitions, captured_flux, 255, true); 74 | 75 | Serial.printf("Write protect: %s\n", floppy.get_write_protect() ? "ON" : "off"); 76 | 77 | delay(100); 78 | } 79 | -------------------------------------------------------------------------------- /examples/greaseweazle/.feather_m4_express_tinyusb.generate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_Floppy/7c489bf56b629d5f01bdf65a83cd6c5f7a098b79/examples/greaseweazle/.feather_m4_express_tinyusb.generate -------------------------------------------------------------------------------- /examples/greaseweazle/.feather_rp2040_tinyusb.generate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_Floppy/7c489bf56b629d5f01bdf65a83cd6c5f7a098b79/examples/greaseweazle/.feather_rp2040_tinyusb.generate -------------------------------------------------------------------------------- /examples/greaseweazle/.feather_rp2350_tinyusb.generate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_Floppy/7c489bf56b629d5f01bdf65a83cd6c5f7a098b79/examples/greaseweazle/.feather_rp2350_tinyusb.generate -------------------------------------------------------------------------------- /examples/greaseweazle/.floppsy_rp2040_tinyusb.generate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_Floppy/7c489bf56b629d5f01bdf65a83cd6c5f7a098b79/examples/greaseweazle/.floppsy_rp2040_tinyusb.generate -------------------------------------------------------------------------------- /examples/mfm_emu/.floppsy_rp2040_tinyusb.generate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_Floppy/7c489bf56b629d5f01bdf65a83cd6c5f7a098b79/examples/mfm_emu/.floppsy_rp2040_tinyusb.generate -------------------------------------------------------------------------------- /examples/mfm_emu/.floppsy_rp2040_tinyusb.test.only: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_Floppy/7c489bf56b629d5f01bdf65a83cd6c5f7a098b79/examples/mfm_emu/.floppsy_rp2040_tinyusb.test.only -------------------------------------------------------------------------------- /examples/mfm_emu/.gitignore: -------------------------------------------------------------------------------- 1 | custom_pinout.h 2 | -------------------------------------------------------------------------------- /examples/mfm_emu/drive.pio: -------------------------------------------------------------------------------- 1 | ; When updating this file, you must manually run `pioasm drive.pio > drive.pio.h`! 2 | ; The Arduino IDE does not do this step! 3 | 4 | .program fluxout_compact 5 | .pio_version 0 6 | .out 1 left auto 32 7 | 8 | .wrap_target 9 | out pins, 1 10 | .wrap 11 | set pins, 0 [3] ;; for FM fluxing, the wrap point is moved just after this one 12 | 13 | %c-sdk { 14 | void sm_config_set_clk_ns(pio_sm_config *c, uint time_ns) { 15 | float f = clock_get_hz(clk_sys) * 1e-9 * time_ns; 16 | int scaled_clkdiv = (int)roundf(f * 256); 17 | sm_config_set_clkdiv_int_frac(c, scaled_clkdiv / 256, scaled_clkdiv % 256); 18 | } 19 | 20 | static inline void fluxout_compact_program_init(PIO pio, uint sm, uint offset, uint pin, uint bit_time_ns) { 21 | pio_sm_config c = fluxout_compact_program_get_default_config(offset); 22 | sm_config_set_out_pins(&c, pin, 1); 23 | sm_config_set_set_pins(&c, pin, 1); 24 | sm_config_set_clk_ns(&c, bit_time_ns); 25 | pio_gpio_init(pio, pin); 26 | pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); 27 | pio_sm_init(pio, sm, offset, &c); 28 | pio_sm_set_enabled(pio, sm, true); 29 | } 30 | %} 31 | 32 | .program index_pulse 33 | .side_set 1 34 | 35 | pull block side 1 36 | mov x, osr side 0 37 | loop: 38 | jmp x--, loop side 0 39 | 40 | % c-sdk { 41 | static inline void index_pulse_program_init(PIO pio, uint sm, uint offset, uint pin, uint bit_time_ns) { 42 | pio_sm_config c = index_pulse_program_get_default_config(offset); 43 | sm_config_set_sideset_pins(&c, pin); 44 | sm_config_set_sideset(&c, 1, /* optional */ false, /* pin direction */ false); 45 | sm_config_set_clk_ns(&c, bit_time_ns); 46 | pio_gpio_init(pio, pin); 47 | pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, /* is_out */ true); 48 | pio_sm_init(pio, sm, offset, &c); 49 | pio_sm_set_enabled(pio, sm, true); 50 | } 51 | %} 52 | -------------------------------------------------------------------------------- /examples/mfm_emu/drive.pio.h: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------- // 2 | // This file is autogenerated by pioasm; do not edit! // 3 | // -------------------------------------------------- // 4 | 5 | #pragma once 6 | 7 | #if !PICO_NO_HARDWARE 8 | #include "hardware/pio.h" 9 | #endif 10 | 11 | // --------------- // 12 | // fluxout_compact // 13 | // --------------- // 14 | 15 | #define fluxout_compact_wrap_target 0 16 | #define fluxout_compact_wrap 0 17 | #define fluxout_compact_pio_version 0 18 | 19 | static const uint16_t fluxout_compact_program_instructions[] = { 20 | // .wrap_target 21 | 0x6001, // 0: out pins, 1 22 | // .wrap 23 | 0xe300, // 1: set pins, 0 [3] 24 | }; 25 | 26 | #if !PICO_NO_HARDWARE 27 | static const struct pio_program fluxout_compact_program = { 28 | .instructions = fluxout_compact_program_instructions, 29 | .length = 2, 30 | .origin = -1, 31 | .pio_version = 0, 32 | #if PICO_PIO_VERSION > 0 33 | .used_gpio_ranges = 0x0 34 | #endif 35 | }; 36 | 37 | static inline pio_sm_config fluxout_compact_program_get_default_config(uint offset) { 38 | pio_sm_config c = pio_get_default_sm_config(); 39 | sm_config_set_wrap(&c, offset + fluxout_compact_wrap_target, offset + fluxout_compact_wrap); 40 | sm_config_set_out_pin_count(&c, 1); 41 | sm_config_set_out_shift(&c, 0, 1, 32); 42 | return c; 43 | } 44 | 45 | void sm_config_set_clk_ns(pio_sm_config *c, uint time_ns) { 46 | float f = clock_get_hz(clk_sys) * 1e-9 * time_ns; 47 | int scaled_clkdiv = (int)roundf(f * 256); 48 | sm_config_set_clkdiv_int_frac(c, scaled_clkdiv / 256, scaled_clkdiv % 256); 49 | } 50 | static inline void fluxout_compact_program_init(PIO pio, uint sm, uint offset, uint pin, uint bit_time_ns) { 51 | pio_sm_config c = fluxout_compact_program_get_default_config(offset); 52 | sm_config_set_out_pins(&c, pin, 1); 53 | sm_config_set_set_pins(&c, pin, 1); 54 | sm_config_set_clk_ns(&c, bit_time_ns); 55 | pio_gpio_init(pio, pin); 56 | pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); 57 | pio_sm_init(pio, sm, offset, &c); 58 | pio_sm_set_enabled(pio, sm, true); 59 | } 60 | 61 | #endif 62 | 63 | // ----------- // 64 | // index_pulse // 65 | // ----------- // 66 | 67 | #define index_pulse_wrap_target 0 68 | #define index_pulse_wrap 2 69 | #define index_pulse_pio_version 0 70 | 71 | static const uint16_t index_pulse_program_instructions[] = { 72 | // .wrap_target 73 | 0x90a0, // 0: pull block side 1 74 | 0xa027, // 1: mov x, osr side 0 75 | 0x0042, // 2: jmp x--, 2 side 0 76 | // .wrap 77 | }; 78 | 79 | #if !PICO_NO_HARDWARE 80 | static const struct pio_program index_pulse_program = { 81 | .instructions = index_pulse_program_instructions, 82 | .length = 3, 83 | .origin = -1, 84 | .pio_version = 0, 85 | #if PICO_PIO_VERSION > 0 86 | .used_gpio_ranges = 0x0 87 | #endif 88 | }; 89 | 90 | static inline pio_sm_config index_pulse_program_get_default_config(uint offset) { 91 | pio_sm_config c = pio_get_default_sm_config(); 92 | sm_config_set_wrap(&c, offset + index_pulse_wrap_target, offset + index_pulse_wrap); 93 | sm_config_set_sideset(&c, 1, false, false); 94 | return c; 95 | } 96 | 97 | static inline void index_pulse_program_init(PIO pio, uint sm, uint offset, uint pin, uint bit_time_ns) { 98 | pio_sm_config c = index_pulse_program_get_default_config(offset); 99 | sm_config_set_sideset_pins(&c, pin); 100 | sm_config_set_sideset(&c, 1, /* optional */ false, /* pin direction */ false); 101 | sm_config_set_clk_ns(&c, bit_time_ns); 102 | pio_gpio_init(pio, pin); 103 | pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, /* is_out */ true); 104 | pio_sm_init(pio, sm, offset, &c); 105 | pio_sm_set_enabled(pio, sm, true); 106 | } 107 | 108 | #endif 109 | 110 | -------------------------------------------------------------------------------- /examples/mfm_emu/mfm_emu.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | #define WAIT_SERIAL 4 | #define XEROX_820 5 | // #define USE_CUSTOM_PINOUT 6 | 7 | #if defined(USE_CUSTOM_PINOUT) && __has_include("custom_pinout.h") 8 | #warning Using custom pinout 9 | #include "custom_pinout.h" 10 | #elif defined(ADAFRUIT_FEATHER_M4_EXPRESS) 11 | #define DENSITY_PIN A1 // IDC 2 12 | #define INDEX_PIN A5 // IDC 8 13 | #define SELECT_PIN A0 // IDC 12 14 | #define MOTOR_PIN A2 // IDC 16 15 | #define DIR_PIN A3 // IDC 18 16 | #define STEP_PIN A4 // IDC 20 17 | #define WRDATA_PIN 13 // IDC 22 18 | #define WRGATE_PIN 12 // IDC 24 19 | #define TRK0_PIN 10 // IDC 26 20 | #define PROT_PIN 11 // IDC 28 21 | #define READ_PIN 9 // IDC 30 22 | #define SIDE_PIN 6 // IDC 32 23 | #define READY_PIN 5 // IDC 34 24 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) || defined(ARDUINO_ADAFRUIT_FEATHER_RP2350_HSTX) 25 | #define DENSITY_PIN A1 // IDC 2 26 | #define INDEX_PIN 25 // IDC 8 27 | #define SELECT_PIN A0 // IDC 12 28 | #define MOTOR_PIN A2 // IDC 16 29 | #define DIR_PIN A3 // IDC 18 30 | #define STEP_PIN 24 // IDC 20 31 | #define WRDATA_PIN 13 // IDC 22 32 | #define WRGATE_PIN 12 // IDC 24 33 | #define TRK0_PIN 10 // IDC 26 34 | #define PROT_PIN 11 // IDC 28 35 | #define READ_PIN 9 // IDC 30 36 | #define SIDE_PIN 8 // IDC 32 37 | #define READY_PIN 7 // IDC 34 38 | #elif defined(ARDUINO_RASPBERRY_PI_PICO) 39 | #define DENSITY_PIN 2 // IDC 2 40 | #define INDEX_PIN 3 // IDC 8 41 | #define SELECT_PIN 4 // IDC 12 42 | #define MOTOR_PIN 5 // IDC 16 43 | #define DIR_PIN 6 // IDC 18 44 | #define STEP_PIN 7 // IDC 20 45 | #define WRDATA_PIN 8 // IDC 22 (not used during read) 46 | #define WRGATE_PIN 9 // IDC 24 (not used during read) 47 | #define TRK0_PIN 10 // IDC 26 48 | #define PROT_PIN 11 // IDC 28 49 | #define READ_PIN 12 // IDC 30 50 | #define SIDE_PIN 13 // IDC 32 51 | #define READY_PIN 14 // IDC 34 52 | #elif defined(ARDUINO_ADAFRUIT_FLOPPSY_RP2040) 53 | // Yay built in pin definitions! 54 | #define NEOPIXEL_PIN PIN_NEOPIXEL 55 | #else 56 | #error "Please set up pin definitions!" 57 | #endif 58 | 59 | #if defined(NEOPIXEL_PIN) 60 | #include 61 | 62 | #ifndef NEOPIXEL_COUNT 63 | #define NEOPIXEL_COUNT (1) 64 | #endif 65 | 66 | #ifndef NEOPIXEL_FORMAT 67 | #define NEOPIXEL_FORMAT NEO_GRB + NEO_KHZ800 68 | #endif 69 | 70 | Adafruit_NeoPixel strip(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEOPIXEL_FORMAT); 71 | 72 | #define STATUS_RGB(r, g, b) \ 73 | do { \ 74 | strip.fill(strip.Color(r, g, b)); \ 75 | strip.show(); \ 76 | } while (0) 77 | #else 78 | #define STATUS_RGB(r, g, b) \ 79 | do { \ 80 | } while (0) 81 | #endif 82 | 83 | #include "drive.pio.h" 84 | #define DEBUG_PRINTF(...) Serial.printf(__VA_ARGS__) 85 | #define DEBUG_ASSERT(x) \ 86 | do { \ 87 | if (!(x)) { \ 88 | Serial.printf(__FILE__ ":%d: Assert fail: " #x "\n", __LINE__); \ 89 | } \ 90 | } while (0) 91 | #include "mfm_impl.h" 92 | 93 | enum { 94 | max_flux_bits = 200000 // 300RPM (200ms rotational time), 1us bit times 95 | }; 96 | enum { max_flux_count_long = (max_flux_bits + 31) / 32 }; 97 | 98 | // Data shared between the two CPU cores 99 | volatile int fluxout; // side number 0/1 or -1 if no flux should be generated 100 | volatile size_t flux_count_long = 101 | max_flux_count_long; // in units of uint32_ts (longs) 102 | volatile uint32_t flux_data[2] 103 | [max_flux_count_long]; // one track of flux data for 104 | // both sides of the disk 105 | 106 | //////////////////////////////// 107 | // Code & data for core 1 108 | // Generate index pulses & flux 109 | //////////////////////////////// 110 | #define FLUX_OUT_PIN (READ_PIN) // "read pin" is named from the controller's POV 111 | 112 | PIO pio = pio0; 113 | uint sm_fluxout, offset_fluxout; 114 | uint sm_index_pulse, offset_index_pulse; 115 | 116 | volatile bool early_setup_done; 117 | 118 | void setup1() { 119 | while (!early_setup_done) { 120 | } 121 | } 122 | 123 | void __not_in_flash_func(loop1)() { 124 | static bool once; 125 | if (fluxout >= 0) { 126 | pio_sm_put_blocking(pio, sm_index_pulse, 127 | 4000); // ??? put index high for 4ms (out of 200ms) 128 | for (size_t i = 0; i < flux_count_long; i++) { 129 | int f = fluxout; 130 | if (f < 0) 131 | break; 132 | auto d = flux_data[fluxout][i]; 133 | pio_sm_put_blocking(pio, sm_fluxout, __builtin_bswap32(d)); 134 | } 135 | // terminate index pulse if ongoing 136 | pio_sm_exec(pio, sm_index_pulse, 137 | 0 | offset_index_pulse); // JMP to the first instruction 138 | } 139 | } 140 | 141 | //////////////////////////////////////////////// 142 | // Code & data for core 0 143 | // "UI", control signal handling & MFM encoding 144 | //////////////////////////////////////////////// 145 | 146 | // Set via IRQ so must be volatile 147 | volatile int trackno; 148 | 149 | enum { 150 | max_sector_count = 18, 151 | mfm_io_block_size = 512, 152 | track_max_bytes = max_sector_count * mfm_io_block_size 153 | }; 154 | 155 | uint8_t track_data[track_max_bytes]; 156 | 157 | void onStep() { 158 | auto enabled = 159 | !digitalRead(SELECT_PIN); // motor need not be enabled to seek tracks 160 | auto direction = digitalRead(DIR_PIN); 161 | int new_track = trackno; 162 | if (direction) { 163 | if (new_track > 0) 164 | new_track--; 165 | } else { 166 | if (new_track < 79) 167 | new_track++; 168 | } 169 | if (!enabled) { 170 | return; 171 | } 172 | trackno = new_track; 173 | digitalWrite(TRK0_PIN, trackno != 0); // active LOW 174 | } 175 | 176 | #if defined(PIN_CARD_CS) 177 | #define USE_SDFAT (1) 178 | #include "SdFat.h" 179 | SdFat SD; 180 | FsFile dir; 181 | FsFile file; 182 | 183 | struct floppy_format_info_t { 184 | uint8_t cylinders, sectors, sides; // number of sides may be 1 or 2 185 | uint16_t bit_time_ns; 186 | size_t flux_count_bit; 187 | uint8_t n; // sector size is 128<is_fm) { 218 | pio_sm_set_wrap(pio, sm_fluxout, offset_fluxout, offset_fluxout + 1); 219 | pio_sm_set_clk_ns(pio, sm_fluxout, i.bit_time_ns / 4); 220 | gpio_set_outover(FLUX_OUT_PIN, GPIO_OVERRIDE_INVERT); 221 | } else { 222 | pio_sm_set_wrap(pio, sm_fluxout, offset_fluxout, offset_fluxout + 0); 223 | pio_sm_set_clk_ns(pio, sm_fluxout, i.bit_time_ns); 224 | gpio_set_outover(FLUX_OUT_PIN, GPIO_OVERRIDE_NORMAL); 225 | } 226 | flux_count_long = (i.flux_count_bit + 31) / 32; 227 | return true; 228 | } 229 | return false; 230 | } 231 | 232 | void openNextImage() { 233 | bool rewound = false; 234 | while (true) { 235 | auto res = file.openNext(&dir, O_RDONLY); 236 | if (!res) { 237 | if (rewound) { 238 | Serial.println("No image found"); 239 | return; 240 | } 241 | dir.rewind(); 242 | rewound = true; 243 | continue; 244 | } 245 | file.printFileSize(&Serial); 246 | Serial.write(' '); 247 | file.printModifyDateTime(&Serial); 248 | Serial.write(' '); 249 | file.printName(&Serial); 250 | if (file.isDir()) { 251 | // Indicate a directory. 252 | Serial.println("/"); 253 | continue; 254 | } 255 | if (setFormat(file.fileSize())) { 256 | Serial.printf(": Valid floppy image\n"); 257 | return; 258 | } else { 259 | Serial.println(": Unrecognized file length\n"); 260 | } 261 | } 262 | } 263 | #endif 264 | 265 | void setup() { 266 | #if defined(FLOPPY_DIRECTION_PIN) 267 | pinMode(FLOPPY_DIRECTION_PIN, OUTPUT); 268 | digitalWrite(FLOPPY_DIRECTION_PIN, LOW); // we are emulating a floppy 269 | #endif 270 | #if defined(FLOPPY_ENABLE_PIN) 271 | pinMode(FLOPPY_ENABLE_PIN, OUTPUT); 272 | digitalWrite(FLOPPY_ENABLE_PIN, LOW); // do second after setting direction 273 | #endif 274 | 275 | offset_fluxout = pio_add_program(pio, &fluxout_compact_program); 276 | sm_fluxout = pio_claim_unused_sm(pio, true); 277 | fluxout_compact_program_init(pio, sm_fluxout, offset_fluxout, FLUX_OUT_PIN, 278 | 1000); 279 | 280 | offset_index_pulse = pio_add_program(pio, &index_pulse_program); 281 | sm_index_pulse = pio_claim_unused_sm(pio, true); 282 | index_pulse_program_init(pio, sm_index_pulse, offset_index_pulse, INDEX_PIN, 283 | 1000); 284 | early_setup_done = true; 285 | 286 | pinMode(DIR_PIN, INPUT_PULLUP); 287 | pinMode(STEP_PIN, INPUT_PULLUP); 288 | pinMode(SIDE_PIN, INPUT_PULLUP); 289 | pinMode(MOTOR_PIN, INPUT_PULLUP); 290 | pinMode(SELECT_PIN, INPUT_PULLUP); 291 | pinMode(TRK0_PIN, OUTPUT); 292 | pinMode(READY_PIN, OUTPUT); 293 | digitalWrite(READY_PIN, HIGH); // active low 294 | #if defined(PROT_PIN) 295 | pinMode(PROT_PIN, OUTPUT); 296 | digitalWrite(PROT_PIN, LOW); // always write-protected, no write support 297 | #endif 298 | #if defined(DISKCHANGE_PIN) 299 | pinMode(DISKCHANGE_PIN, INPUT_PULLUP); 300 | #endif 301 | 302 | Serial.begin(115200); 303 | #if defined(WAIT_SERIAL) 304 | while (!Serial) { 305 | } 306 | Serial.println("Serial connected"); 307 | #endif 308 | 309 | #if defined(XEROX_820) 310 | pinMode(DENSITY_PIN, OUTPUT); 311 | digitalWrite(DENSITY_PIN, 312 | HIGH); // Xerox 820 density select HIGH means 8" floppy 313 | pinMode(READY_PIN, OUTPUT); 314 | digitalWrite(READY_PIN, LOW); // Drive always reports readiness 315 | Serial.println("Configured for Xerox 820 8\" floppy emulation"); 316 | #endif 317 | 318 | attachInterrupt(digitalPinToInterrupt(STEP_PIN), onStep, FALLING); 319 | 320 | #if defined(NEOPIXEL_PIN) 321 | strip.begin(); 322 | #endif 323 | 324 | #if USE_SDFAT 325 | if (!SD.begin(PIN_CARD_CS)) { 326 | Serial.println("SD card initialization failed"); 327 | STATUS_RGB(255, 0, 0); 328 | delay(2000); 329 | } else if (!dir.open("/")) { 330 | Serial.println("SD card directory could not be read"); 331 | STATUS_RGB(255, 255, 0); 332 | delay(2000); 333 | } else { 334 | STATUS_RGB(0, 0, 255); 335 | openNextImage(); 336 | } 337 | #endif 338 | } 339 | 340 | static void encode_track(uint8_t head, uint8_t cylinder) { 341 | 342 | mfm_io_t io = { 343 | .encode_compact = true, 344 | .pulses = (uint8_t *)flux_data[head], 345 | .n_pulses = flux_count_long * sizeof(long), 346 | .sectors = track_data, 347 | .n_sectors = cur_format->sectors, 348 | .head = head, 349 | .cylinder = cylinder, 350 | .n = cur_format->n, 351 | .settings = cur_format->is_fm ? &standard_fm : &standard_mfm, 352 | }; 353 | 354 | size_t pos = encode_track_mfm(&io); 355 | Serial.printf("Encoded to %zu flux\n", pos); 356 | } 357 | 358 | // As an easter egg, the dummy disk image embeds the boot sector Tetris 359 | // implementation from https://github.com/daniel-e/tetros (source available 360 | // under MIT license) 361 | const uint8_t tetros[] = { 362 | #include "tetros.h" 363 | }; 364 | 365 | static void make_dummy_data(uint8_t head, uint8_t cylinder, size_t n_bytes) { 366 | uint8_t dummy_byte = head * 2 + cylinder; 367 | std::fill(track_data, track_data + n_bytes, dummy_byte); 368 | if (head == 0 && cylinder == 0 && n_bytes >= 512) { 369 | Serial.println("Injecting tetros in boot sector"); 370 | std::copy(tetros, std::end(tetros), track_data); 371 | } 372 | } 373 | 374 | void loop() { 375 | static int cached_trackno = -1; 376 | auto new_trackno = trackno; 377 | int motor_pin = !digitalRead(MOTOR_PIN); 378 | int select_pin = !digitalRead(SELECT_PIN); 379 | int side = !digitalRead(SIDE_PIN); 380 | 381 | #if defined(XEROX_820) 382 | // no separate motor pin on this baby 383 | motor_pin = true; 384 | // only one side 385 | side = 0; 386 | #endif 387 | 388 | auto enabled = motor_pin && select_pin; 389 | static bool old_enabled = false, old_select_pin = false, 390 | old_motor_pin = false; 391 | 392 | if (motor_pin != old_motor_pin) { 393 | Serial.printf("motor_pin -> %s\n", motor_pin ? "true" : "false"); 394 | old_motor_pin = motor_pin; 395 | } 396 | if (select_pin != old_select_pin) { 397 | Serial.printf("select_pin -> %s\n", select_pin ? "true" : "false"); 398 | old_select_pin = select_pin; 399 | } 400 | 401 | if (enabled != old_enabled) { 402 | Serial.printf("enabled -> %s\n", enabled ? "true" : "false"); 403 | old_enabled = enabled; 404 | } 405 | #if defined(DISKCHANGE_PIN) && USE_SDFAT 406 | int diskchange_pin = digitalRead(DISKCHANGE_PIN); 407 | static int diskchange_pin_delayed = false; 408 | auto diskchange = diskchange_pin_delayed && !diskchange_pin; 409 | diskchange_pin_delayed = diskchange_pin; 410 | if (diskchange) { 411 | delay(20); 412 | while (!digitalRead(DISKCHANGE_PIN)) { /* NOTHING */ 413 | } 414 | fluxout = -1; 415 | cached_trackno = -1; 416 | openNextImage(); 417 | } 418 | #endif 419 | 420 | if (cur_format && new_trackno != cached_trackno) { 421 | STATUS_RGB(0, 255, 255); 422 | fluxout = -1; 423 | Serial.printf("Preparing flux data for track %d\n", new_trackno); 424 | int sector_count = cur_format->sectors; 425 | int side_count = cur_format->sides; 426 | int sector_size = 128 << cur_format->n; 427 | size_t offset = sector_size * sector_count * side_count * new_trackno; 428 | size_t count = sector_size * sector_count; 429 | int dummy_byte = new_trackno * side_count; 430 | #if USE_SDFAT 431 | file.seek(offset); 432 | for (auto side = 0; side < side_count; side++) { 433 | int n = file.read(track_data, count); 434 | if (n != count) { 435 | Serial.println("Read failed -- using dummy data"); 436 | make_dummy_data(side, new_trackno, count); 437 | } 438 | encode_track(side, new_trackno); 439 | } 440 | #else 441 | Serial.println("No filesystem - using dummy data"); 442 | for (auto side = 0; side < side_count; side++) { 443 | make_dummy_data(side, new_trackno, count); 444 | encode_track(side, new_trackno); 445 | } 446 | #endif 447 | 448 | Serial.println("flux data prepared"); 449 | cached_trackno = new_trackno; 450 | } 451 | fluxout = 452 | (cur_format != NULL && enabled && cached_trackno == trackno) ? side : -1; 453 | #if defined(NEOPIXEL_PIN) 454 | if (fluxout >= 0) { 455 | STATUS_RGB(0, 1, 0); 456 | } else { 457 | STATUS_RGB(0, 0, 0); 458 | } 459 | #endif 460 | 461 | // this is not correct handling of the ready/disk change flag. on my test 462 | // computer, just leaving the pin HIGH works, while immediately reporting LOW 463 | // on the "ready / disk change: 464 | #if 0 465 | digitalWrite(READY_PIN, !motor_pin); 466 | #endif 467 | } 468 | -------------------------------------------------------------------------------- /examples/mfm_emu/tetros.h: -------------------------------------------------------------------------------- 1 | 49, 192, 142, 216, 49, 192, 205, 16, 180, 1, 185, 7, 38, 205, 16, 182, 3, 185, 2 | 18, 0, 81, 254, 198, 178, 13, 185, 14, 0, 187, 120, 0, 232, 182, 0, 128, 3 | 254, 21, 116, 9, 66, 185, 12, 0, 49, 219, 232, 168, 0, 89, 226, 225, 198, 6, 4 | 0, 127, 100, 180, 2, 205, 26, 160, 2, 127, 49, 208, 179, 31, 247, 227, 64, 5 | 162, 2, 127, 49, 210, 187, 7, 0, 247, 243, 192, 226, 3, 146, 186, 18, 4, 6 | 232, 229, 0, 117, 254, 232, 210, 0, 49, 201, 138, 14, 0, 127, 81, 96, 49, 7 | 201, 186, 184, 11, 180, 134, 205, 21, 97, 80, 180, 1, 205, 22, 137, 193, 88, 8 | 116, 69, 232, 175, 0, 128, 253, 75, 116, 17, 128, 253, 72, 116, 30, 128, 9 | 253, 77, 116, 16, 198, 6, 0, 127, 10, 235, 35, 74, 232, 167, 0, 116, 29, 66, 10 | 235, 26, 66, 232, 158, 0, 116, 20, 74, 235, 17, 136, 195, 64, 64, 168, 7, 11 | 117, 2, 44, 8, 232, 140, 0, 116, 2, 136, 216, 232, 119, 0, 80, 48, 228, 205, 12 | 22, 88, 89, 226, 162, 232, 103, 0, 254, 198, 232, 116, 0, 116, 138, 254, 13 | 206, 232, 95, 0, 232, 22, 0, 233, 91, 255, 180, 2, 205, 16, 184, 32, 9, 205, 14 | 16, 195, 180, 2, 205, 16, 180, 8, 205, 16, 195, 96, 182, 21, 254, 206, 116, 15 | 57, 49, 219, 185, 12, 0, 178, 14, 232, 230, 255, 192, 236, 4, 116, 2, 67, 16 | 66, 226, 244, 128, 251, 12, 117, 228, 96, 178, 14, 185, 12, 0, 81, 254, 206, 17 | 232, 204, 255, 254, 198, 136, 227, 177, 1, 232, 185, 255, 66, 89, 226, 237, 18 | 97, 254, 206, 117, 226, 232, 192, 255, 97, 195, 49, 219, 235, 9, 136, 195, 19 | 192, 235, 3, 67, 192, 227, 4, 67, 137, 223, 235, 3, 191, 0, 0, 96, 49, 219, 20 | 136, 195, 139, 135, 134, 125, 49, 219, 185, 4, 0, 81, 177, 4, 246, 196, 128, 21 | 116, 29, 80, 9, 255, 116, 14, 96, 137, 251, 48, 192, 185, 1, 0, 232, 112, 22 | 255, 97, 235, 9, 232, 116, 255, 192, 236, 4, 116, 1, 67, 88, 209, 224, 66, 23 | 226, 217, 128, 234, 4, 254, 198, 89, 226, 206, 8, 219, 97, 195, 68, 68, 0, 24 | 240, 68, 68, 0, 240, 96, 34, 0, 226, 64, 100, 0, 142, 96, 68, 0, 46, 32, 98, 25 | 0, 232, 0, 102, 0, 102, 0, 102, 0, 102, 0, 198, 64, 38, 0, 198, 64, 38, 0, 26 | 78, 64, 76, 0, 228, 128, 140, 0, 108, 64, 140, 0, 108, 64, 140, 128, 0, 1, 27 | 0, 23, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 170 30 | -------------------------------------------------------------------------------- /host_src/.gitignore: -------------------------------------------------------------------------------- 1 | test_flux.h 2 | main 3 | main_fm 4 | flux[0-9] 5 | fluxfm* 6 | check[0-9] 7 | checkfm* 8 | decode[0-9] 9 | decodefm* 10 | -------------------------------------------------------------------------------- /host_src/Makefile: -------------------------------------------------------------------------------- 1 | PYTHON3 = python3 2 | 3 | .PHONY: all 4 | all: check checkfm 5 | 6 | .PHONY: check 7 | check: main check_flux.py 8 | ./main 9 | $(PYTHON3) check_flux.py flux0 > decode0 10 | $(PYTHON3) check_flux.py flux1 > decode1 11 | $(PYTHON3) check_flux.py flux2 > decode2 12 | 13 | .PHONY: checkfm 14 | checkfm: main_fm check_flux.py 15 | ./main_fm 16 | $(PYTHON3) check_flux.py --fm fluxfm > decodefm 17 | 18 | main: main.c ../src/mfm_impl.h Makefile test_flux.h 19 | gcc -iquote ../src -Wall -Werror -ggdb3 -Og -o $@ $< 20 | 21 | main_fm: main_fm.c ../src/mfm_impl.h Makefile 22 | gcc -iquote ../src -Wall -Werror -ggdb3 -Og -o $@ $< 23 | 24 | test_flux.h: make_flux.py greaseweazle/scripts/greaseweazle/version.py 25 | $(PYTHON3) $< $@ 26 | 27 | greaseweazle/scripts/greaseweazle/version.py: 28 | $(MAKE) -C greaseweazle 29 | -------------------------------------------------------------------------------- /host_src/check_flux.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | sys.path.insert(0, str( 4 | pathlib.Path(__file__).parent / "greaseweazle/src")) 5 | import click 6 | from greaseweazle.codec.codec import get_diskdef 7 | from greaseweazle.track import MasterTrack, PLLTrack 8 | from bitarray import bitarray 9 | 10 | @click.command 11 | @click.option("--fm/--no-fm", is_flag=True) 12 | @click.argument("flux-file") 13 | def main(flux_file, fm=False): 14 | print(f"{flux_file=!r}") 15 | with open(flux_file) as flux1: 16 | content = bitarray("".join(c for c in flux1.read() if c in "01")) 17 | 18 | print(content.count(0), content.count(1)) 19 | 20 | if fm: 21 | master = MasterTrack(content[:83_500], .166) 22 | track = get_diskdef("dec.rx01").mk_track(0,0) 23 | track.time_per_rev = .166 24 | track.clock = 2e-6 25 | else: 26 | master = MasterTrack(content[:200_000], .200) 27 | track = get_diskdef("ibm.1440").mk_track(0,0) 28 | track.time_per_rev = 0.2 29 | track.clock = 1e-6 30 | 31 | track.decode_flux(master, None) 32 | print(flux_file, track.summary_string(), file=sys.stderr) 33 | print(flux_file, track.summary_string()) 34 | print("".join("E."[sec.crc == 0] for sec in track.sectors)) 35 | for i in track.iams: 36 | print(i) 37 | for s in track.sectors: 38 | print(s) 39 | 40 | if n := track.nr_missing(): 41 | print(f"{n} missing sector(s)", file=sys.stderr) 42 | raise SystemExit(1) 43 | 44 | if __name__ == '__main__': 45 | main() 46 | -------------------------------------------------------------------------------- /host_src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define DEBUG_PRINTF(...) printf(__VA_ARGS__) 6 | #include "mfm_impl.h" 7 | 8 | uint8_t flux[] = { 9 | #include "test_flux.h" 10 | }; 11 | 12 | enum { sector_count = 18 }; 13 | enum { ibmpc_io_block_size = 512 }; 14 | uint8_t track_buf[sector_count * ibmpc_io_block_size]; 15 | uint8_t validity[sector_count]; 16 | 17 | mfm_io_t io = { 18 | .T1_nom = 2, 19 | .T2_max = 5, 20 | .T3_max = 7, 21 | .pulses = flux, 22 | .n_pulses = sizeof(flux), 23 | .sectors = track_buf, 24 | .sector_validity = validity, 25 | .n_sectors = sector_count, 26 | .n = 2, 27 | .settings = &standard_mfm, 28 | .encode_raw = mfm_io_encode_raw_mfm, 29 | }; 30 | 31 | static void flux_bins(mfm_io_t *io) { 32 | io->pos = 0; 33 | int bins[3] = {}; 34 | while (!mfm_io_eof(io)) { 35 | bins[mfm_io_read_symbol(io)]++; 36 | } 37 | printf("Flux bins: %d %d %d\n", bins[0], bins[1], bins[2]); 38 | } 39 | 40 | static void dump_flux_compact(const char *filename, mfm_io_t *io) { 41 | FILE *f = fopen(filename, "w"); 42 | io->pos = 0; 43 | while (!mfm_io_eof(io)) { 44 | int b = io->pulses[io->pos++]; 45 | for (int i = 8; i-- > 0;) { 46 | fputc('0' + ((b >> i) & 1), f); 47 | }; 48 | fputc(io->pos % 8 == 0 ? '\n' : ' ', f); 49 | } 50 | fclose(f); 51 | } 52 | static void dump_flux(const char *filename, mfm_io_t *io) { 53 | FILE *f = fopen(filename, "w"); 54 | io->pos = 0; 55 | uint32_t state = 0; 56 | while (!mfm_io_eof(io)) { 57 | int s = mfm_io_read_symbol(io); 58 | state = ((state << 2) | s) & mfm_io_triple_mark_mask; 59 | fprintf(f, "10"); 60 | if (s > mfm_io_pulse_10) { 61 | fprintf(f, "0"); 62 | } 63 | if (s > mfm_io_pulse_100) { 64 | fprintf(f, "0"); 65 | } 66 | if (state == mfm_io_triple_mark_magic) { 67 | DEBUG_PRINTF("triple mark @%zd\n", io->pos); 68 | fprintf(f, "\n"); 69 | } 70 | } 71 | fclose(f); 72 | } 73 | 74 | int main() { 75 | flux_bins(&io); 76 | printf("Decoded %zd sectors\n", decode_track_mfm(&io)); 77 | 78 | dump_flux("flux0", &io); 79 | 80 | memset(flux, 0, sizeof(flux)); 81 | 82 | #if 0 83 | for (size_t i = 0; i < sizeof(track_buf); i++) 84 | track_buf[i] = i & 0xff; 85 | #endif 86 | 87 | printf("Create new flux data\n"); 88 | encode_track_mfm(&io); 89 | dump_flux("flux1", &io); 90 | 91 | memset(track_buf, 0, sizeof(track_buf)); 92 | 93 | io.n_valid = 0; 94 | memset(validity, 0, sizeof(validity)); 95 | flux_bins(&io); 96 | size_t decoded = decode_track_mfm(&io); 97 | printf("Decoded %zd sectors\n", decoded); 98 | 99 | io.encode_compact = true; 100 | encode_track_mfm(&io); 101 | dump_flux_compact("flux2", &io); 102 | 103 | return decoded != 18; 104 | } 105 | -------------------------------------------------------------------------------- /host_src/main_fm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define DEBUG_PRINTF(...) printf(__VA_ARGS__) 6 | #include "mfm_impl.h" 7 | 8 | enum { sector_count = 26 }; 9 | enum { block_size = 128 }; 10 | 11 | uint8_t flux[10000]; 12 | uint8_t track_buf[sector_count * block_size]; 13 | uint8_t validity[sector_count]; 14 | 15 | mfm_io_t io = { 16 | .T1_nom = 2, 17 | .T2_max = 5, 18 | .T3_max = 7, 19 | .pulses = flux, 20 | .n_pulses = sizeof(flux), 21 | .sectors = track_buf, 22 | .sector_validity = validity, 23 | .n_sectors = sector_count, 24 | .n = 0, 25 | .settings = &standard_fm, 26 | .encode_raw = mfm_io_encode_raw_fm, 27 | .encode_compact = true, 28 | }; 29 | 30 | static void dump_flux_compact(const char *filename, mfm_io_t *io) { 31 | FILE *f = fopen(filename, "w"); 32 | io->pos = 0; 33 | while (!mfm_io_eof(io)) { 34 | int b = io->pulses[io->pos++]; 35 | for (int i = 8; i-- > 0;) { 36 | fputc('0' + ((b >> i) & 1), f); 37 | }; 38 | if (io->pos % 2 == 0) { 39 | fputc('\n', f); 40 | } 41 | } 42 | fclose(f); 43 | } 44 | 45 | int main() { 46 | for (size_t i = 0; i < sector_count; i++) { 47 | memset(track_buf + i * block_size, block_size, 'A' + i); 48 | } 49 | 50 | size_t r = encode_track_mfm(&io); 51 | printf("Used flux %zd\n", r); 52 | dump_flux_compact("fluxfm", &io); 53 | } 54 | -------------------------------------------------------------------------------- /host_src/make_flux.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | sys.path.insert(0, str( 4 | pathlib.Path(__file__).parent / "greaseweazle/src")) 5 | 6 | from greaseweazle.codec.codec import get_diskdef 7 | 8 | track = get_diskdef("ibm.1440").mk_track(0,0) 9 | track.set_img_track(b'adaf00' + b'\0' * 512 * 18) 10 | #track.decode_raw(track) 11 | print(track.summary_string()) 12 | flux = track.flux() 13 | print(flux.list[:25],len(flux.list)) 14 | with open(sys.argv[1], "wt") as f: 15 | for i, fi in enumerate(flux.list): 16 | print(f"{fi*2},", end="\n" if i % 16 == 15 else " ", file=f) 17 | print(file=f) 18 | -------------------------------------------------------------------------------- /host_src/make_flux_fm.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | sys.path.insert(0, str( 4 | pathlib.Path(__file__).parent / "greaseweazle/src")) 5 | 6 | from greaseweazle.codec.ibm.fm import IBM_FM_Predefined 7 | 8 | class RX01(IBM_FM_Predefined): 9 | id0 = 1 10 | nsec = 26 11 | sz = 0 12 | cskew = 1 13 | gap_3 = 27 # old GW has 26 but we want the same gap3 that mfm_impl will use 14 | time_per_rev = 60/360 15 | clock = 4e-6 16 | 17 | def convertflux(flux): 18 | for x in flux: 19 | yield 1 20 | for i in range(x-1): 21 | yield 0 22 | 23 | if __name__ == '__main__': 24 | track = RX01(0, 0) 25 | trackdata = b''.join(bytes([65 + i]) * 128 for i in range(RX01.nsec)) 26 | track.set_img_track(trackdata) 27 | track.decode_raw(track) 28 | print(track.summary_string()) 29 | flux = track.flux() 30 | with open(sys.argv[1], "wt") as f: 31 | for i, fi in enumerate(convertflux(flux.list[1:])): 32 | print(f"{fi}", end="\n" if i % 16 == 15 else "", file=f) 33 | print(file=f) 34 | -------------------------------------------------------------------------------- /images/rabbit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_Floppy/7c489bf56b629d5f01bdf65a83cd6c5f7a098b79/images/rabbit.png -------------------------------------------------------------------------------- /images/readme: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Adafruit Floppy 2 | version=0.6.1 3 | author=Adafruit 4 | maintainer=Adafruit 5 | sentence=Adafruit's floppy disk drive interfacing library 6 | paragraph=Adafruit's floppy disk drive interfacing library 7 | category=Communication 8 | url=https://github.com/adafruit/Adafruit_Floppy 9 | architectures=* 10 | depends=Adafruit BusIO, SdFat - Adafruit Fork, Adafruit ST7735 and ST7789 Library,Adafruit NeoPixel 11 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Software License Agreement (MIT License) 2 | 3 | Copyright (c) 2021, Limor Fried for Adafruit Industries 4 | All rights reserved. 5 | 6 | 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: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | 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. -------------------------------------------------------------------------------- /src/Adafruit_Floppy.h: -------------------------------------------------------------------------------- 1 | #ifndef ADAFRUIT_FLOPPY_H 2 | #define ADAFRUIT_FLOPPY_H 3 | 4 | /*! \mainpage 5 | * 6 | * \image html rabbit.png 7 | * 8 | * This is a helper library to abstract away interfacing with floppy disk drives 9 | * in a cross-platform and open source library. 10 | * 11 | * Adafruit Floppy is a project to make a flexible, full-stack, open source 12 | * hardware/software device for reading, archiving, accessing and duplicating 13 | * floppy disk media. It joins a family of open source hardware and software 14 | * such as greaseweazle and fluxengine, and increases the availability and 15 | * accessibility of floppy disk controllers. 16 | */ 17 | 18 | #include "Arduino.h" 19 | #include 20 | // to implement SdFat Block Driver 21 | // 22 | #define DISABLE_FS_H_WARNING 23 | #include "SdFat.h" 24 | #include "SdFatConfig.h" 25 | 26 | #define FLOPPY_IBMPC_HD_TRACKS 80 27 | #define FLOPPY_IBMPC_DD_TRACKS 40 28 | #define FLOPPY_HEADS 2 29 | 30 | #define MFM_IBMPC1200K_SECTORS_PER_TRACK 15 31 | #define MFM_IBMPC1440K_SECTORS_PER_TRACK 18 32 | #define MFM_IBMPC360K_SECTORS_PER_TRACK 9 33 | #define MFM_IBMPC720K_SECTORS_PER_TRACK 9 34 | #define MFM_BYTES_PER_SECTOR 512UL 35 | 36 | #define STEP_OUT HIGH 37 | #define STEP_IN LOW 38 | #define MAX_FLUX_PULSE_PER_TRACK \ 39 | (uint32_t)(500000UL / 5 * \ 40 | 1.5) // 500khz / 5 hz per track rotation, 1.5 rotations 41 | 42 | #define BUSTYPE_IBMPC 1 43 | #define BUSTYPE_SHUGART 2 44 | 45 | typedef enum { 46 | IBMPC360K, 47 | IBMPC720K, 48 | IBMPC720K_360RPM, 49 | IBMPC1200K, 50 | IBMPC1440K, 51 | IBMPC1440K_360RPM, 52 | AUTODETECT, 53 | } adafruit_floppy_disk_t; 54 | 55 | /**************************************************************************/ 56 | /*! 57 | @brief An abstract base class for chattin with floppy drives 58 | */ 59 | /**************************************************************************/ 60 | class Adafruit_FloppyBase { 61 | protected: 62 | Adafruit_FloppyBase(int indexpin, int wrdatapin, int wrgatepin, int rddatapin, 63 | bool is_apple2 = false); 64 | 65 | public: 66 | bool begin(void); 67 | virtual void end(); 68 | 69 | virtual void soft_reset(void); 70 | 71 | /**************************************************************************/ 72 | /*! 73 | @brief Whether to select this drive 74 | @param selected True to select/enable 75 | */ 76 | /**************************************************************************/ 77 | virtual void select(bool selected) = 0; 78 | /**************************************************************************/ 79 | /*! 80 | @brief Is the drive selected based on interal caching 81 | @returns True if the drive is selected, false otherwise 82 | */ 83 | /**************************************************************************/ 84 | bool drive_is_selected(void) { return is_drive_selected; } 85 | 86 | /**************************************************************************/ 87 | /*! 88 | @brief Turn on or off the floppy motor, if on we wait till we get an 89 | index pulse! 90 | @param motor_on True to turn on motor, False to turn it off 91 | @returns False if turning motor on and no index pulse found, true 92 | otherwise 93 | */ 94 | /**************************************************************************/ 95 | virtual bool spin_motor(bool motor_on) = 0; 96 | /**************************************************************************/ 97 | /*! 98 | @brief Is the drive motor spinning based on interal caching 99 | @returns True if the motor is spinning, false otherwise 100 | */ 101 | /**************************************************************************/ 102 | bool motor_is_spinning(void) { return is_motor_spinning; } 103 | /**************************************************************************/ 104 | /*! 105 | @brief Are index pulses being seen? 106 | @returns True if we're seeing index pulses, false otherwise 107 | */ 108 | /**************************************************************************/ 109 | bool index_pulses_seen(void) { return is_index_seen; } 110 | /**************************************************************************/ 111 | /*! 112 | @brief Seek to the desired track, requires the motor to be spun up! 113 | @param track_num The track to step to 114 | @return True If we were able to get to the track location 115 | */ 116 | /**************************************************************************/ 117 | virtual bool goto_track(int track_num) = 0; 118 | /**************************************************************************/ 119 | /*! 120 | @brief Which head/side to read from 121 | @param head Head 0 or 1 122 | @return true if the head exists, false otherwise 123 | */ 124 | /**************************************************************************/ 125 | virtual bool side(int head) = 0; 126 | /**************************************************************************/ 127 | /*! 128 | @brief Current head in use, based on internal caching 129 | @return Head 0 or 1 130 | */ 131 | /**************************************************************************/ 132 | virtual int get_side() = 0; 133 | /**************************************************************************/ 134 | /*! 135 | @brief The current track location, based on internal caching 136 | @return The cached track location 137 | @note Returns -1 if the track is not known. 138 | */ 139 | /**************************************************************************/ 140 | virtual int track(void) = 0; 141 | /**************************************************************************/ 142 | /*! 143 | @brief Check whether the floppy in the drive is write protected 144 | @returns False if the floppy is writable, true otherwise 145 | */ 146 | /**************************************************************************/ 147 | virtual bool get_write_protect() = 0; 148 | 149 | /**************************************************************************/ 150 | /*! 151 | @brief Check whether the track0 sensor is active 152 | @returns True if the track0 sensor is active, false otherwise 153 | @note On devices without a track0 sensor, this returns true when 154 | track()==0 155 | */ 156 | /**************************************************************************/ 157 | virtual bool get_track0_sense() = 0; 158 | 159 | /**************************************************************************/ 160 | /*! 161 | @brief Check whether the ready output is active 162 | @returns True if the ready sensor is active, false otherwise 163 | @note On devices without a ready sensor, this always returns true 164 | */ 165 | /**************************************************************************/ 166 | virtual bool get_ready_sense() = 0; 167 | 168 | /**************************************************************************/ 169 | /*! 170 | @brief Set the density for flux reading and writing 171 | @param high_density false for low density, true for high density 172 | @returns True if the drive interface supports the given density. 173 | */ 174 | /**************************************************************************/ 175 | virtual bool set_density(bool high_density) = 0; 176 | 177 | size_t decode_track_mfm(uint8_t *sectors, size_t n_sectors, 178 | uint8_t *sector_validity, const uint8_t *pulses, 179 | size_t n_pulses, float nominal_bit_time_us, 180 | bool clear_validity = false, 181 | uint8_t *logical_track = nullptr); 182 | 183 | size_t encode_track_mfm(const uint8_t *sectors, size_t n_sectors, 184 | uint8_t *pulses, size_t max_pulses, 185 | float nominal_bit_time_us, uint8_t logical_track); 186 | 187 | size_t capture_track(volatile uint8_t *pulses, size_t max_pulses, 188 | int32_t *falling_index_offset, 189 | bool store_greaseweazle = false, uint32_t capture_ms = 0, 190 | uint32_t index_wait_ms = 250) 191 | __attribute__((optimize("O3"))); 192 | 193 | bool write_track(uint8_t *pulses, size_t n_pulses, 194 | bool store_greaseweazle = false, bool use_index = true) 195 | __attribute__((optimize("O3"))); 196 | void print_pulse_bins(uint8_t *pulses, size_t n_pulses, uint8_t max_bins = 64, 197 | bool is_gw_format = false, uint32_t min_bin_size = 100); 198 | void print_pulses(uint8_t *pulses, size_t n_pulses, 199 | bool is_gw_format = false); 200 | uint32_t getSampleFrequency(void); 201 | 202 | #if defined(LED_BUILTIN) 203 | int8_t led_pin = LED_BUILTIN; ///< Debug LED output for tracing 204 | #else 205 | int8_t led_pin = -1; ///< Debug LED output for tracing 206 | #endif 207 | 208 | uint16_t select_delay_us = 10; ///< delay after drive select (usecs) 209 | uint16_t step_delay_us = 10000; ///< delay between head steps (usecs) 210 | uint16_t settle_delay_ms = 15; ///< settle delay after seek (msecs) 211 | uint16_t motor_delay_ms = 1000; ///< delay after motor on (msecs) 212 | uint16_t watchdog_delay_ms = 213 | 1000; ///< quiescent time until drives reset (msecs) 214 | uint8_t bus_type = BUSTYPE_IBMPC; ///< what kind of floppy drive we're using 215 | 216 | Stream *debug_serial = nullptr; ///< optional debug stream for serial output 217 | 218 | protected: 219 | bool read_index(); 220 | bool is_drive_selected; ///< cached drive select state 221 | bool is_motor_spinning; ///< cached motor spinning state 222 | bool is_index_seen; ///< cached index pulses seen state 223 | 224 | private: 225 | #if defined(__SAMD51__) 226 | void deinit_capture(void); 227 | void enable_capture(void); 228 | 229 | bool init_generate(void); 230 | void deinit_generate(void); 231 | void enable_generate(void); 232 | void disable_generate(void); 233 | #endif 234 | 235 | bool start_polled_capture(void); 236 | void disable_capture(void); 237 | 238 | bool init_capture(void); 239 | void enable_background_capture(void); 240 | void wait_for_index_pulse_low(void); 241 | 242 | int8_t _indexpin, _wrdatapin, _wrgatepin, _rddatapin; 243 | bool _is_apple2; 244 | 245 | #ifdef BUSIO_USE_FAST_PINIO 246 | BusIO_PortReg *indexPort; 247 | BusIO_PortMask indexMask; 248 | uint32_t dummyPort = 0; 249 | #endif 250 | }; 251 | 252 | /**************************************************************************/ 253 | /*! 254 | @brief A helper class for chattin with PC & Shugart floppy drives 255 | */ 256 | /**************************************************************************/ 257 | class Adafruit_Floppy : public Adafruit_FloppyBase { 258 | public: 259 | Adafruit_Floppy(int8_t densitypin, int8_t indexpin, int8_t selectpin, 260 | int8_t motorpin, int8_t directionpin, int8_t steppin, 261 | int8_t wrdatapin, int8_t wrgatepin, int8_t track0pin, 262 | int8_t protectpin, int8_t rddatapin, int8_t sidepin, 263 | int8_t readypin); 264 | void end() override; 265 | void soft_reset(void) override; 266 | 267 | void select(bool selected) override; 268 | bool spin_motor(bool motor_on) override; 269 | bool goto_track(int track) override; 270 | bool side(int head) override; 271 | int track(void) override; 272 | int get_side(void) override; 273 | void step(bool dir, uint8_t times); 274 | bool set_density(bool high_density) override; 275 | bool get_write_protect() override; 276 | bool get_track0_sense() override; 277 | bool get_ready_sense() override; 278 | 279 | private: 280 | // theres a lot of GPIO! 281 | int8_t _densitypin, _selectpin, _motorpin, _directionpin, _steppin, 282 | _track0pin, _protectpin, _sidepin, _readypin; 283 | 284 | int _track = -1, _side = -1; 285 | }; 286 | 287 | /**************************************************************************/ 288 | /*! 289 | @brief A helper class for chattin with Apple 2 floppy drives 290 | */ 291 | /**************************************************************************/ 292 | class Adafruit_Apple2Floppy : public Adafruit_FloppyBase { 293 | public: 294 | /**************************************************************************/ 295 | /*! 296 | @brief Constants for use with the step_mode method 297 | */ 298 | /**************************************************************************/ 299 | enum StepMode { 300 | STEP_MODE_WHOLE, //< One step moves by one data track 301 | STEP_MODE_HALF, //< Two steps move by one data track 302 | STEP_MODE_QUARTER, //< Four steps move by one data track 303 | }; 304 | 305 | Adafruit_Apple2Floppy(int8_t indexpin, int8_t selectpin, int8_t phase1pin, 306 | int8_t phase2pin, int8_t phase3pin, int8_t phase4pin, 307 | int8_t wrdatapin, int8_t wrgatepin, int8_t protectpin, 308 | int8_t rddatapin); 309 | void end() override; 310 | void soft_reset(void) override; 311 | 312 | void select(bool selected) override; 313 | bool spin_motor(bool motor_on) override; 314 | bool goto_track(int track) override; 315 | bool side(int head) override; 316 | int track(void) override; 317 | bool set_density(bool high_density) override; 318 | bool get_write_protect() override; 319 | bool get_track0_sense() override; 320 | bool get_ready_sense() override { return true; } 321 | 322 | int quartertrack(); 323 | bool goto_quartertrack(int); 324 | void step_mode(StepMode mode); 325 | 326 | int get_side() override { return 0; } 327 | 328 | private: 329 | int _step_multiplier() const; 330 | // theres not much GPIO! 331 | int8_t _selectpin, _phase1pin, _phase2pin, _phase3pin, _phase4pin, 332 | _protectpin; 333 | int _quartertrack = -1; 334 | StepMode _step_mode = STEP_MODE_HALF; 335 | void _step(int dir, int times); 336 | }; 337 | 338 | /**************************************************************************/ 339 | /*! 340 | This class adds support for the BaseBlockDriver interface to an MFM 341 | encoded floppy disk. This allows it to be used with SdFat's FatFileSystem 342 | class. or for a mass storage device 343 | */ 344 | /**************************************************************************/ 345 | class Adafruit_MFM_Floppy : public FsBlockDeviceInterface { 346 | public: 347 | Adafruit_MFM_Floppy(Adafruit_Floppy *floppy, 348 | adafruit_floppy_disk_t format = AUTODETECT); 349 | 350 | bool begin(void); 351 | void end(void); 352 | 353 | uint32_t size(void) const; 354 | int32_t readTrack(int track, bool head); 355 | 356 | /**! @brief The expected number of sectors per track in this format 357 | @returns The number of sectors per track */ 358 | uint8_t sectors_per_track(void) const { return _sectors_per_track; } 359 | /**! @brief The expected number of tracks per side in this format 360 | @returns The number of tracks per side */ 361 | uint8_t tracks_per_side(void) const { return _tracks_per_side; } 362 | 363 | /**! @brief Check if there is data to be written to the current track 364 | @returns True if data needs to be written out */ 365 | bool dirty() const { return _dirty; } 366 | 367 | /**! @brief Call when the media has been removed */ 368 | void removed(); 369 | /**! @brief Call when media has been inserted 370 | @param format The hard coded format or AUTODETECT to try several common 371 | formats 372 | @returns True if media is hard coded or if the media was detected by 373 | autodetect */ 374 | bool inserted(adafruit_floppy_disk_t format); 375 | 376 | //------------- SdFat v2 FsBlockDeviceInterface API -------------// 377 | virtual bool isBusy(); 378 | virtual uint32_t sectorCount(); 379 | virtual bool syncDevice(); 380 | 381 | virtual bool readSector(uint32_t block, uint8_t *dst); 382 | virtual bool readSectors(uint32_t block, uint8_t *dst, size_t ns); 383 | virtual bool writeSector(uint32_t block, const uint8_t *src); 384 | virtual bool writeSectors(uint32_t block, const uint8_t *src, size_t ns); 385 | 386 | /**! The raw byte decoded data from the last track read */ 387 | uint8_t track_data[MFM_IBMPC1440K_SECTORS_PER_TRACK * MFM_BYTES_PER_SECTOR]; 388 | 389 | /**! Which tracks from the last track-read were valid MFM/CRC! */ 390 | uint8_t track_validity[MFM_IBMPC1440K_SECTORS_PER_TRACK]; 391 | 392 | private: 393 | bool autodetect(); 394 | #if defined(PICO_BOARD) || defined(__RP2040__) || defined(ARDUINO_ARCH_RP2040) 395 | uint16_t _last; 396 | #endif 397 | static constexpr uint8_t NO_TRACK = UINT8_MAX; 398 | uint8_t _sectors_per_track = 0; 399 | uint8_t _tracks_per_side = 0; 400 | uint8_t _last_track_read = NO_TRACK; // last cached track 401 | uint16_t _bit_time_ns; 402 | bool _high_density = true; 403 | bool _dirty = false, _track_has_errors = false; 404 | bool _double_step = false; 405 | Adafruit_Floppy *_floppy = nullptr; 406 | adafruit_floppy_disk_t _format = AUTODETECT; 407 | 408 | /**! The raw flux data from the last track read */ 409 | uint8_t _flux[125000]; 410 | size_t _n_flux; 411 | }; 412 | 413 | #endif 414 | -------------------------------------------------------------------------------- /src/Adafruit_MFM_Floppy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /// @cond false 4 | static const uint16_t flux_rates[] = {2000, 1000, 867, 1667}; 5 | 6 | struct adafruit_floppy_format_info_t { 7 | uint8_t cylinders, sectors; 8 | uint16_t bit_time_ns; 9 | uint16_t track_time_ms; 10 | }; 11 | 12 | // must match the order of adafruit_floppy_disk_t 13 | static const adafruit_floppy_format_info_t _format_info[] = { 14 | /* IBMPC360K */ 15 | {40, 9, 2000, 167}, 16 | /* IBMPC1200K */ 17 | {80, 15, 1000, 200}, 18 | 19 | /* IBMPC720K */ 20 | {80, 9, 1000, 200}, 21 | /* IBMPC720K_360RPM */ 22 | {80, 9, 1667, 167}, 23 | 24 | /* IBMPC1440K */ 25 | {80, 18, 1000, 200}, 26 | /* IBMPC1440K_360RPM */ 27 | {80, 18, 867, 167}, 28 | }; 29 | /// @endcond 30 | 31 | static_assert(sizeof(_format_info) / sizeof(_format_info[0]) == AUTODETECT); 32 | 33 | /**************************************************************************/ 34 | /*! 35 | @brief Instantiate an MFM-formatted floppy 36 | @param floppy An Adafruit_Floppy object that has the pins defined 37 | @param format What kind of format we will be parsing out - we DO NOT 38 | autodetect! 39 | */ 40 | /**************************************************************************/ 41 | Adafruit_MFM_Floppy::Adafruit_MFM_Floppy(Adafruit_Floppy *floppy, 42 | adafruit_floppy_disk_t format) { 43 | _floppy = floppy; 44 | _format = format; 45 | 46 | // different formats have different 'hardcoded' sectors and tracks 47 | if (_format == IBMPC1440K) { 48 | _sectors_per_track = MFM_IBMPC1440K_SECTORS_PER_TRACK; 49 | _tracks_per_side = FLOPPY_IBMPC_HD_TRACKS; 50 | _high_density = true; 51 | } else if (_format == IBMPC360K) { 52 | _sectors_per_track = MFM_IBMPC360K_SECTORS_PER_TRACK; 53 | _tracks_per_side = FLOPPY_IBMPC_DD_TRACKS; 54 | _high_density = false; 55 | } 56 | } 57 | 58 | /**************************************************************************/ 59 | /*! 60 | @brief Initialize and spin up the floppy drive 61 | @returns True if we were able to spin up and detect an index track 62 | */ 63 | /**************************************************************************/ 64 | bool Adafruit_MFM_Floppy::begin(void) { 65 | if (!_floppy) 66 | return false; 67 | _floppy->begin(); 68 | 69 | // now's the time to tweak settings 70 | if (_format == IBMPC360K) { 71 | _floppy->step_delay_us = 65000UL; // lets make it max 65ms not 10ms? 72 | _floppy->settle_delay_ms = 50; // 50ms not 15 73 | } 74 | 75 | _floppy->select(true); 76 | 77 | if (_floppy->spin_motor(true)) { 78 | return inserted(_format); 79 | } else { 80 | return false; 81 | } 82 | 83 | return true; 84 | } 85 | 86 | /**************************************************************************/ 87 | /*! 88 | @brief Spin down and deselect the motor and drive 89 | */ 90 | /**************************************************************************/ 91 | void Adafruit_MFM_Floppy::end(void) { 92 | _floppy->spin_motor(false); 93 | _floppy->select(false); 94 | } 95 | 96 | /**************************************************************************/ 97 | /*! 98 | @brief Quick calculator for expected max capacity 99 | @returns Size of the drive in bytes 100 | */ 101 | /**************************************************************************/ 102 | uint32_t Adafruit_MFM_Floppy::size(void) const { 103 | return (uint32_t)_tracks_per_side * FLOPPY_HEADS * _sectors_per_track * 104 | MFM_BYTES_PER_SECTOR; 105 | } 106 | 107 | /**************************************************************************/ 108 | /*! 109 | @brief Read one track's worth of data and MFM decode it 110 | @param logical_track the logical track number, 0 to whatever is the max 111 | tracks for the given format during instantiation (e.g. 40 for DD, 80 for HD) 112 | @param head which side to read, false for side 1, true for side 2 113 | @returns Number of sectors captured, or -1 if we couldn't seek 114 | */ 115 | /**************************************************************************/ 116 | int32_t Adafruit_MFM_Floppy::readTrack(int logical_track, bool head) { 117 | syncDevice(); 118 | 119 | uint8_t physical_track = _double_step ? 2 * logical_track : logical_track; 120 | 121 | Serial.printf("\t[readTrack] Seeking track %d [phys=%d] head %d...\r\n", 122 | logical_track, physical_track, head); 123 | if (!_floppy->goto_track(physical_track)) { 124 | // Serial.println("failed to seek to track"); 125 | return -1; 126 | } 127 | _floppy->side(head); 128 | // Serial.println("done!"); 129 | // flux not decoding from a 3.5" floppy? Maybe it's rotating at 360RPM instead 130 | // of 300RPM see e.g., 131 | // https://www.retrotechnology.com/herbs_stuff/drive.html#rotate2 132 | // and change nominal bit time to 0.833 ~= 300/360 133 | // would be good to auto-detect! 134 | uint32_t captured_sectors = 0; 135 | for (int i = 0; i < 5 && captured_sectors < _sectors_per_track; i++) { 136 | int32_t index_offset; 137 | _n_flux = 138 | _floppy->capture_track(_flux, sizeof(_flux), &index_offset, false, 220); 139 | captured_sectors = _floppy->decode_track_mfm(track_data, _sectors_per_track, 140 | track_validity, _flux, _n_flux, 141 | _bit_time_ns / 1000.f, i == 0); 142 | } 143 | 144 | _track_has_errors = (captured_sectors != _sectors_per_track); 145 | if (_track_has_errors) { 146 | Serial.printf("Track %d/%d has errors (%d != %d)\n", logical_track, head, 147 | captured_sectors, _sectors_per_track); 148 | } 149 | _last_track_read = logical_track * FLOPPY_HEADS + head; 150 | return captured_sectors; 151 | } 152 | 153 | //--------------------------------------------------------------------+ 154 | // SdFat BaseBlockDriver API 155 | // A block is 512 bytes 156 | //--------------------------------------------------------------------+ 157 | 158 | /**************************************************************************/ 159 | /*! 160 | @brief Max capacity in sector block 161 | @returns Size of the drive in sector (512 bytes) 162 | */ 163 | /**************************************************************************/ 164 | uint32_t Adafruit_MFM_Floppy::sectorCount() { 165 | return size() / MFM_BYTES_PER_SECTOR; 166 | } 167 | 168 | /**************************************************************************/ 169 | /*! 170 | @brief Check if device busy 171 | @returns true if busy 172 | */ 173 | /**************************************************************************/ 174 | bool Adafruit_MFM_Floppy::isBusy() { 175 | // since writing is not supported yet 176 | return false; 177 | } 178 | 179 | /**************************************************************************/ 180 | /*! 181 | @brief Read a 512 byte block of data, may used cached data 182 | @param block Block number, will be split into head and track based on 183 | expected formatting 184 | @param dst Destination buffer 185 | @returns True on success 186 | */ 187 | /**************************************************************************/ 188 | bool Adafruit_MFM_Floppy::readSector(uint32_t block, uint8_t *dst) { 189 | if (block > sectorCount()) { 190 | return false; 191 | } 192 | 193 | uint8_t track = block / (FLOPPY_HEADS * _sectors_per_track); 194 | uint8_t head = (block / _sectors_per_track) % FLOPPY_HEADS; 195 | uint8_t subsector = block % _sectors_per_track; 196 | 197 | // Serial.printf("\tRead request block %d\n", block); 198 | if ((track * FLOPPY_HEADS + head) != _last_track_read) { 199 | // oof it is not cached! 200 | 201 | if (readTrack(track, head) == -1) { 202 | return false; 203 | } 204 | } 205 | 206 | if (!track_validity[subsector]) { 207 | // Serial.println("subsector invalid"); 208 | return false; 209 | } 210 | // Serial.println("OK!"); 211 | memcpy(dst, track_data + (subsector * MFM_BYTES_PER_SECTOR), 212 | MFM_BYTES_PER_SECTOR); 213 | 214 | return true; 215 | } 216 | 217 | /**************************************************************************/ 218 | /*! 219 | @brief Read multiple 512 byte block of data, may used cached data 220 | @param block Starting block number, will be split into head and track based 221 | on expected formatting 222 | @param dst Destination buffer 223 | @param nb Number of blocks to read 224 | @returns True on success 225 | */ 226 | /**************************************************************************/ 227 | bool Adafruit_MFM_Floppy::readSectors(uint32_t block, uint8_t *dst, size_t nb) { 228 | // read each block one by one 229 | for (size_t blocknum = 0; blocknum < nb; blocknum++) { 230 | if (!readSector(block + blocknum, dst + (blocknum * MFM_BYTES_PER_SECTOR))) 231 | return false; 232 | } 233 | return true; 234 | } 235 | 236 | /**************************************************************************/ 237 | /*! 238 | @brief Write a 512 byte block of data NOT IMPLEMENTED YET 239 | @param block Block number, will be split into head and track based on 240 | expected formatting 241 | @param src Source buffer 242 | @returns True on success, false if failed or unimplemented 243 | */ 244 | /**************************************************************************/ 245 | bool Adafruit_MFM_Floppy::writeSector(uint32_t block, const uint8_t *src) { 246 | if (block > sectorCount()) { 247 | return false; 248 | } 249 | 250 | // promptly fail if disk is protected 251 | // might also fail if WGATE is masked by HW (e.g., by physical switch on 252 | // floppsy) 253 | if (_floppy->get_write_protect()) { 254 | return false; 255 | } 256 | 257 | uint8_t track = block / (FLOPPY_HEADS * _sectors_per_track); 258 | uint8_t head = (block / _sectors_per_track) % FLOPPY_HEADS; 259 | uint8_t subsector = block % _sectors_per_track; 260 | 261 | if ((track * FLOPPY_HEADS + head) != _last_track_read) { 262 | // oof it is not cached! 263 | 264 | if (readTrack(track, head) == -1) { 265 | return false; 266 | } 267 | 268 | _last_track_read = track * FLOPPY_HEADS + head; 269 | } 270 | Serial.printf("Writing block %d\r\n", block); 271 | track_validity[subsector] = 1; 272 | memcpy(track_data + (subsector * MFM_BYTES_PER_SECTOR), src, 273 | MFM_BYTES_PER_SECTOR); 274 | _dirty = true; 275 | return true; 276 | } 277 | 278 | /**************************************************************************/ 279 | /*! 280 | @brief Write multiple 512 byte blocks of data NOT IMPLEMENTED YET 281 | @param block Starting lock number, will be split into head and track based 282 | on expected formatting 283 | @param src Source buffer 284 | @param nb Number of consecutive blocks to write 285 | @returns True on success, false if failed or unimplemented 286 | */ 287 | /**************************************************************************/ 288 | bool Adafruit_MFM_Floppy::writeSectors(uint32_t block, const uint8_t *src, 289 | size_t nb) { 290 | // write each block one by one 291 | for (size_t blocknum = 0; blocknum < nb; blocknum++) { 292 | if (!writeSector(block + blocknum, src + (blocknum * MFM_BYTES_PER_SECTOR))) 293 | return false; 294 | } 295 | return true; 296 | } 297 | 298 | /**************************************************************************/ 299 | /*! 300 | @brief Sync written blocks NOT IMPLEMENTED YET 301 | @returns True on success, false if failed or unimplemented 302 | */ 303 | /**************************************************************************/ 304 | bool Adafruit_MFM_Floppy::syncDevice() { 305 | if (!_dirty || _last_track_read == NO_TRACK) { 306 | return true; 307 | } 308 | _dirty = false; 309 | 310 | int logical_track = _last_track_read / FLOPPY_HEADS; 311 | int head = _last_track_read % FLOPPY_HEADS; 312 | 313 | uint8_t physical_track = _double_step ? 2 * logical_track : logical_track; 314 | Serial.printf("Flushing track %d [phys %d] side %d\r\n", logical_track, 315 | physical_track, head); 316 | // should be a no-op 317 | if (!_floppy->goto_track(physical_track)) { 318 | Serial.println("failed to seek to track"); 319 | return false; 320 | } 321 | 322 | if (!_floppy->side(head)) { 323 | Serial.println("failed to select head"); 324 | return false; 325 | } 326 | 327 | bool has_errors = false; 328 | for (size_t i = 0; !has_errors && i < _sectors_per_track; i++) { 329 | has_errors = !track_validity[i]; 330 | } 331 | 332 | if (has_errors) { 333 | Serial.printf( 334 | "Can't do a non-full track write to track with read errors\n"); 335 | return false; 336 | } 337 | _n_flux = _floppy->encode_track_mfm(track_data, _sectors_per_track, _flux, 338 | sizeof(_flux), _high_density ? 1.f : 2.f, 339 | logical_track); 340 | 341 | if (!_floppy->write_track(_flux, _n_flux, false)) { 342 | Serial.println("failed to write track"); 343 | return false; 344 | } 345 | 346 | return true; 347 | } 348 | 349 | void Adafruit_MFM_Floppy::removed() { 350 | noInterrupts(); 351 | _tracks_per_side = 0; 352 | _last_track_read = NO_TRACK; 353 | _dirty = false; 354 | interrupts(); 355 | } 356 | 357 | static uint16_t le16_at(uint8_t *ptr) { return ptr[0] | (ptr[1] << 8); } 358 | 359 | bool Adafruit_MFM_Floppy::autodetect() { 360 | Serial.printf("autodetecting\r\n"); 361 | int32_t index_offset; 362 | _n_flux = _floppy->capture_track(_flux, sizeof(_flux) / 16, &index_offset, 363 | false, 220); 364 | for (auto flux_rate_ns : flux_rates) { 365 | Serial.printf("flux rate %d\r\n", flux_rate_ns); 366 | auto captured_sectors = 367 | _floppy->decode_track_mfm(track_data, 1, track_validity, _flux, _n_flux, 368 | flux_rate_ns / 1000.f, true); 369 | if (captured_sectors) { 370 | auto valid_signature = 371 | track_data[0] == 0xeb && // short jump 372 | track_data[1] >= 0x1e && // minimum BPB size (DOS 3.0 BPB) 373 | track_data[2] == 0x90; // NOP 374 | if (!valid_signature) { 375 | Serial.printf("Invalid signature %02x %02x %02x\r\n", track_data[0], 376 | track_data[1], track_data[2]); 377 | continue; 378 | } 379 | auto heads = le16_at(track_data + 0x1A); 380 | auto total_logical_sectors = le16_at(track_data + 0x13); 381 | 382 | _bit_time_ns = flux_rate_ns; 383 | _sectors_per_track = le16_at(track_data + 0x18); 384 | _tracks_per_side = total_logical_sectors / heads / _sectors_per_track; 385 | _last_track_read = NO_TRACK; 386 | _dirty = false; 387 | 388 | if (_tracks_per_side <= 40) { 389 | _floppy->goto_track(2); 390 | _n_flux = _floppy->capture_track(_flux, sizeof(_flux) / 16, 391 | &index_offset, false, 220); 392 | uint8_t track_number; 393 | auto captured_sectors = _floppy->decode_track_mfm( 394 | track_data, 1, track_validity, _flux, _n_flux, 395 | flux_rate_ns / 1000.f, true, &track_number); 396 | if (!captured_sectors) { 397 | Serial.printf("failed to read on physical track 2\r\n"); 398 | } 399 | _double_step = (track_number == 1); 400 | Serial.printf( 401 | "on physical track 2, track_number=%d. _double_step <- %d\r\n", 402 | track_number, _double_step); 403 | } else { 404 | _double_step = false; 405 | } 406 | Serial.printf("Detected flux rate %dns/bit\r\n%d/%d/%d C/H/S\r\n", 407 | flux_rate_ns, _tracks_per_side, heads, _sectors_per_track); 408 | return true; 409 | } 410 | } 411 | Serial.printf("failed autodetect\r\n"); 412 | _sectors_per_track = 0; 413 | return false; 414 | } 415 | 416 | bool Adafruit_MFM_Floppy::inserted(adafruit_floppy_disk_t floppy_type) { 417 | _floppy->goto_track(0); 418 | _floppy->side(0); 419 | 420 | if (floppy_type == AUTODETECT) { 421 | return autodetect(); 422 | } else if (floppy_type < 0 || floppy_type > AUTODETECT) { 423 | return false; 424 | } 425 | const auto &info = _format_info[floppy_type]; 426 | 427 | noInterrupts(); 428 | _tracks_per_side = info.cylinders; 429 | _sectors_per_track = info.sectors; 430 | _bit_time_ns = info.bit_time_ns; 431 | _last_track_read = NO_TRACK; 432 | _dirty = false; 433 | interrupts(); 434 | 435 | return true; 436 | // TODO: set up double stepping on HD 5.25 drives with 360kB media inserted 437 | } 438 | -------------------------------------------------------------------------------- /src/arch_rp2.cpp: -------------------------------------------------------------------------------- 1 | #if defined(PICO_BOARD) || defined(__RP2040__) || defined(ARDUINO_ARCH_RP2040) 2 | #include "arch_rp2.h" 3 | #include "greasepack.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | static const int fluxread_sideset_pin_count = 0; 13 | static const bool fluxread_sideset_enable = 0; 14 | static const uint16_t fluxread[] = { 15 | // ; Count flux pulses and watch for index pin 16 | // ; flux input is the 'jmp pin'. index is "pin zero". 17 | // ; Counts are in units 3 / F_pio, so e.g., at 30MHz 1 count = 0.1us 18 | // ; Count down while waiting for the counter to go HIGH 19 | // ; The only counting is down, so C code will just have to negate the 20 | // count! 21 | // ; Each 'wait one' loop takes 3 instruction-times 22 | // wait_one: 23 | 0x0041, // jmp x--, wait_one_next ; acts as a non-conditional decrement 24 | // of x 25 | // wait_one_next: 26 | 0x00c3, // jmp pin wait_zero 27 | 0x0000, // jmp wait_one 28 | // ; Each 'wait zero' loop takes 3 instruction-times, needing one 29 | // instruction delay 30 | // ; (it has to match the 'wait one' timing exactly) 31 | // wait_zero: 32 | 0x0044, // jmp x--, wait_zero_next ; acts as a non-conditional decrement 33 | // of x 34 | // wait_zero_next: 35 | 0x01c3, // jmp pin wait_zero [1] 36 | // ; Top bit is index status, bottom 15 bits are inverse of counts 37 | // ; Combined FIFO gives 16 entries (8 32-bit entries) so with the 38 | // ; smallest plausible pulse of 2us there are 250 CPU cycles available 39 | // @125MHz 40 | 0x4001, // in pins, 1 41 | 0x402f, // in x, 15 42 | // ; Threee cycles for the end of loop, so we need to decrement x to make 43 | // everything 44 | // ; come out right. This has constant timing whether we actually jump back 45 | // vs wrapping. 46 | 0x0040, // jmp x--, wait_one 47 | }; 48 | static const pio_program_t fluxread_struct = {.instructions = fluxread, 49 | .length = sizeof(fluxread) / 50 | sizeof(fluxread[0]), 51 | .origin = -1}; 52 | 53 | static const int fluxwrite_sideset_pin_count = 0; 54 | static const bool fluxwrite_sideset_enable = 0; 55 | static const uint16_t fluxwrite[] = { 56 | // loop_flux: 57 | 0xe000, // set pins, 0 ; drive pin low 58 | 0x6030, // out x, 16 ; get the next timing pulse information, may block 59 | // ;; output the fixed on time. 16 is about 670ns. 60 | // ;; note that wdc1772 has varying low times, from 570 to 1380ns 61 | 0xae42, // nop [14] 62 | 0xe001, // set pins, 1 ; drive pin high 63 | // loop_high: 64 | 0x0044, // jmp x--, loop_high 65 | 0x0000, // jmp loop_flux 66 | }; 67 | static const pio_program_t fluxwrite_struct = {.instructions = fluxwrite, 68 | .length = sizeof(fluxwrite) / 69 | sizeof(fluxwrite[0]), 70 | .origin = -1}; 71 | 72 | static const int fluxwrite_apple2_sideset_pin_count = 0; 73 | static const bool fluxwrite_apple2_sideset_enable = 0; 74 | static const uint16_t fluxwrite_apple2[] = { 75 | 0x6030, // out x, 16 ; get the next timing pulse information, may block 76 | 0xe001, // set pins, 1 ; drive pin high 77 | 0xb042, // nop [16] 78 | // loop_high: 79 | 0x0043, // jmp x--, loop_high 80 | 0x6030, // out x, 16 ; get the next timing pulse information, may block 81 | 0xe000, // set pins, 0 ; drive pin low 82 | 0xb042, // nop [16] 83 | // loop_low: 84 | 0x0047, // jmp x--, loop_low 85 | }; 86 | static const pio_program_t fluxwrite_apple2_struct = { 87 | .instructions = fluxwrite_apple2, 88 | .length = sizeof(fluxwrite_apple2) / sizeof(fluxwrite_apple2[0]), 89 | .origin = -1}; 90 | 91 | typedef struct floppy_singleton { 92 | PIO pio; 93 | const pio_program_t *program; 94 | unsigned sm; 95 | uint16_t offset; 96 | uint16_t half; 97 | } floppy_singleton_t; 98 | 99 | static floppy_singleton_t g_reader, g_writer; 100 | 101 | const static PIO pio_instances[2] = {pio0, pio1}; 102 | static bool allocate_pio_set_program(floppy_singleton_t *info, 103 | const pio_program_t *program) { 104 | memset(info, 0, sizeof(*info)); 105 | for (size_t i = 0; i < NUM_PIOS; i++) { 106 | PIO pio = pio_instances[i]; 107 | if (!pio_can_add_program(pio, program)) { 108 | continue; 109 | } 110 | int sm = pio_claim_unused_sm(pio, false); 111 | if (sm != -1) { 112 | info->pio = pio; 113 | info->sm = sm; 114 | // cannot fail, we asked nicely already 115 | info->offset = pio_add_program(pio, program); 116 | return true; 117 | } 118 | } 119 | return false; 120 | } 121 | 122 | static bool init_capture(int index_pin, int read_pin) { 123 | if (g_reader.pio) { 124 | return true; 125 | } 126 | 127 | if (!allocate_pio_set_program(&g_reader, &fluxread_struct)) { 128 | return false; 129 | } 130 | 131 | gpio_pull_up(index_pin); 132 | 133 | pio_sm_config c = {0, 0, 0}; 134 | sm_config_set_wrap(&c, g_reader.offset, 135 | g_reader.offset + fluxread_struct.length - 1); 136 | sm_config_set_jmp_pin(&c, read_pin); 137 | sm_config_set_in_pins(&c, index_pin); 138 | sm_config_set_in_shift(&c, true, true, 32); 139 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); 140 | pio_sm_set_pins_with_mask(g_reader.pio, g_reader.sm, 1 << read_pin, 141 | 1 << read_pin); 142 | float div = (float)clock_get_hz(clk_sys) / (3 * 24e6); 143 | sm_config_set_clkdiv(&c, div); // 72MHz capture clock / 24MHz sample rate 144 | 145 | pio_sm_init(g_reader.pio, g_reader.sm, g_reader.offset, &c); 146 | 147 | return true; 148 | } 149 | 150 | static void start_common() { 151 | pio_sm_exec(g_reader.pio, g_reader.sm, g_reader.offset); 152 | pio_sm_restart(g_reader.pio, g_reader.sm); 153 | } 154 | 155 | static bool data_available() { 156 | return g_reader.half || !pio_sm_is_rx_fifo_empty(g_reader.pio, g_reader.sm); 157 | } 158 | 159 | static uint16_t read_fifo() { 160 | if (g_reader.half) { 161 | uint16_t result = g_reader.half; 162 | g_reader.half = 0; 163 | return result; 164 | } 165 | uint32_t value = pio_sm_get_blocking(g_reader.pio, g_reader.sm); 166 | g_reader.half = value >> 16; 167 | return value & 0xffff; 168 | } 169 | 170 | static void disable_capture(void) { 171 | pio_sm_set_enabled(g_reader.pio, g_reader.sm, false); 172 | } 173 | 174 | static void free_capture(void) { 175 | if (!g_reader.pio) { 176 | // already deinit 177 | return; 178 | } 179 | disable_capture(); 180 | pio_sm_unclaim(g_reader.pio, g_reader.sm); 181 | pio_remove_program(g_reader.pio, &fluxread_struct, g_reader.offset); 182 | memset(&g_reader, 0, sizeof(g_reader)); 183 | } 184 | 185 | static uint8_t *capture_foreground(int index_pin, uint8_t *start, uint8_t *end, 186 | int32_t *falling_index_offset, 187 | bool store_greaseweazle, 188 | uint32_t capture_counts, 189 | uint32_t max_wait_time) { 190 | uint8_t *ptr = start; 191 | if (falling_index_offset) { 192 | *falling_index_offset = -1; 193 | } 194 | start_common(); 195 | 196 | // wait for a falling edge of index pin, then enable the capture peripheral 197 | if (max_wait_time) { 198 | uint32_t start_time = millis(); 199 | while (!gpio_get(index_pin)) { /* NOTHING */ 200 | if (millis() - start_time > max_wait_time) { 201 | disable_capture(); 202 | return ptr; 203 | } 204 | } 205 | while (gpio_get(index_pin)) { /* NOTHING */ 206 | if (millis() - start_time > max_wait_time) { 207 | disable_capture(); 208 | return ptr; 209 | } 210 | } 211 | } 212 | 213 | uint32_t total_counts = 0; 214 | 215 | noInterrupts(); 216 | pio_sm_clear_fifos(g_reader.pio, g_reader.sm); 217 | pio_sm_set_enabled(g_reader.pio, g_reader.sm, true); 218 | int last = read_fifo(); 219 | bool last_index = gpio_get(index_pin); 220 | while (ptr != end) { 221 | /* Handle index */ 222 | bool now_index = gpio_get(index_pin); 223 | 224 | if (!now_index && last_index) { 225 | if (falling_index_offset) { 226 | *falling_index_offset = ptr - start; 227 | if (!capture_counts) { 228 | break; 229 | } 230 | } 231 | } 232 | last_index = now_index; 233 | 234 | if (!data_available()) { 235 | continue; 236 | } 237 | 238 | int data = read_fifo(); 239 | int delta = last - data; 240 | if (delta < 0) 241 | delta += 65536; 242 | delta /= 2; 243 | 244 | last = data; 245 | total_counts += delta; 246 | if (store_greaseweazle) { 247 | ptr = greasepack(ptr, end, delta); 248 | } else { 249 | *ptr++ = delta > 255 ? 255 : delta; 250 | } 251 | if (capture_counts != 0 && total_counts >= capture_counts) { 252 | break; 253 | } 254 | } 255 | interrupts(); 256 | 257 | disable_capture(); 258 | 259 | return ptr; 260 | } 261 | 262 | static void enable_capture_fifo() { start_common(); } 263 | 264 | static bool init_write(int wrdata_pin, bool is_apple2) { 265 | if (g_writer.pio) { 266 | return true; 267 | } 268 | 269 | const pio_program_t *program = 270 | is_apple2 ? &fluxwrite_apple2_struct : &fluxwrite_struct; 271 | g_writer.program = program; 272 | 273 | if (!allocate_pio_set_program(&g_writer, program)) { 274 | return false; 275 | } 276 | 277 | uint32_t wrdata_bit = 1u << wrdata_pin; 278 | 279 | pio_gpio_init(g_writer.pio, wrdata_pin); 280 | 281 | pio_sm_set_pindirs_with_mask(g_writer.pio, g_writer.sm, wrdata_bit, 282 | wrdata_bit); 283 | pio_sm_set_pins_with_mask(g_writer.pio, g_writer.sm, wrdata_bit, wrdata_bit); 284 | pio_sm_set_pins_with_mask(g_writer.pio, g_writer.sm, 0, wrdata_bit); 285 | pio_sm_set_pins_with_mask(g_writer.pio, g_writer.sm, wrdata_bit, wrdata_bit); 286 | 287 | pio_sm_config c{}; 288 | sm_config_set_wrap(&c, g_writer.offset, 289 | g_writer.offset + program->length - 1); 290 | sm_config_set_set_pins(&c, wrdata_pin, 1); 291 | sm_config_set_out_shift(&c, true, true, 16); 292 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); 293 | float div = (float)clock_get_hz(clk_sys) / (24e6); 294 | sm_config_set_clkdiv(&c, div); // 24MHz output clock 295 | 296 | pio_sm_init(g_writer.pio, g_writer.sm, g_writer.offset, &c); 297 | 298 | return true; 299 | } 300 | 301 | static void enable_write() {} 302 | 303 | #define OVERHEAD (20) // minimum pulse length due to PIO overhead, about 0.833us 304 | static void write_fifo(unsigned value) { 305 | if (value < OVERHEAD) { 306 | value = 1; 307 | } else { 308 | value -= OVERHEAD; 309 | if (value > 0xffff) 310 | value = 0xffff; 311 | } 312 | pio_sm_put_blocking(g_writer.pio, g_writer.sm, value); 313 | } 314 | 315 | static void disable_write() { 316 | pio_sm_set_enabled(g_writer.pio, g_writer.sm, false); 317 | } 318 | 319 | static void write_foreground(int index_pin, int wrgate_pin, uint8_t *pulses, 320 | uint8_t *pulse_end, bool store_greaseweazle, 321 | bool use_index) { 322 | 323 | if (use_index) { 324 | // don't start during an index pulse 325 | while (!gpio_get(index_pin)) { /* NOTHING */ 326 | } 327 | 328 | // wait for falling edge of index pin 329 | while (gpio_get(index_pin)) { /* NOTHING */ 330 | } 331 | } 332 | pinMode(wrgate_pin, OUTPUT); 333 | digitalWrite(wrgate_pin, LOW); 334 | 335 | noInterrupts(); 336 | pio_sm_set_enabled(g_writer.pio, g_writer.sm, false); 337 | pio_sm_clear_fifos(g_writer.pio, g_writer.sm); 338 | pio_sm_exec(g_writer.pio, g_writer.sm, g_writer.offset); 339 | while (!pio_sm_is_tx_fifo_full(g_writer.pio, g_writer.sm)) { 340 | unsigned value = greaseunpack(&pulses, pulse_end, store_greaseweazle); 341 | value = (value < OVERHEAD) ? 1 : value - OVERHEAD; 342 | pio_sm_put_blocking(g_writer.pio, g_writer.sm, value); 343 | } 344 | pio_sm_set_enabled(g_writer.pio, g_writer.sm, true); 345 | 346 | bool old_index_state = false; 347 | while (pulses != pulse_end) { 348 | bool index_state = gpio_get(index_pin); 349 | if (old_index_state && !index_state) { 350 | // falling edge of index pin 351 | break; 352 | } 353 | while (!pio_sm_is_tx_fifo_full(g_writer.pio, g_writer.sm)) { 354 | unsigned value = greaseunpack(&pulses, pulse_end, store_greaseweazle); 355 | value = (value < OVERHEAD) ? 1 : value - OVERHEAD; 356 | pio_sm_put_blocking(g_writer.pio, g_writer.sm, value); 357 | } 358 | old_index_state = index_state; 359 | } 360 | interrupts(); 361 | 362 | pio_sm_set_enabled(g_writer.pio, g_writer.sm, false); 363 | pinMode(wrgate_pin, INPUT_PULLUP); 364 | } 365 | 366 | static void free_write() { 367 | if (!g_writer.pio) { 368 | // already deinit 369 | return; 370 | } 371 | disable_write(); 372 | pio_sm_unclaim(g_writer.pio, g_writer.sm); 373 | pio_remove_program(g_writer.pio, g_writer.program, g_writer.offset); 374 | memset(&g_writer, 0, sizeof(g_writer)); 375 | } 376 | 377 | #ifdef __cplusplus 378 | #include 379 | 380 | uint32_t rp2040_flux_capture(int index_pin, int rdpin, volatile uint8_t *pulses, 381 | volatile uint8_t *pulse_end, 382 | int32_t *falling_index_offset, 383 | bool store_greaseweazle, uint32_t capture_counts, 384 | uint32_t index_wait_ms) { 385 | if (!init_capture(index_pin, rdpin)) { 386 | return 0; 387 | } 388 | 389 | auto result = 390 | capture_foreground(index_pin, (uint8_t *)pulses, (uint8_t *)pulse_end, 391 | falling_index_offset, store_greaseweazle, 392 | capture_counts, index_wait_ms) - 393 | pulses; 394 | free_capture(); 395 | return result; 396 | } 397 | 398 | unsigned _last = ~0u; 399 | bool Adafruit_FloppyBase::init_capture(void) { 400 | _last = ~0u; 401 | return ::init_capture(_indexpin, _rddatapin); 402 | } 403 | 404 | bool Adafruit_FloppyBase::start_polled_capture(void) { 405 | if (!init_capture()) 406 | return false; 407 | start_common(); 408 | pio_sm_set_enabled(g_reader.pio, g_reader.sm, true); 409 | return true; 410 | } 411 | 412 | void Adafruit_FloppyBase::disable_capture(void) { ::disable_capture(); } 413 | 414 | uint16_t mfm_io_sample_flux(bool *index) { 415 | if (_last == ~0u) { 416 | _last = read_fifo(); 417 | } 418 | int data = read_fifo(); 419 | int delta = _last - data; 420 | _last = data; 421 | if (delta < 0) 422 | delta += 65536; 423 | *index = data & 1; 424 | return delta / 2; 425 | } 426 | 427 | bool rp2040_flux_write(int index_pin, int wrgate_pin, int wrdata_pin, 428 | uint8_t *pulses, uint8_t *pulse_end, 429 | bool store_greaseweazle, bool is_apple2, 430 | bool use_index) { 431 | if (!init_write(wrdata_pin, is_apple2)) { 432 | return false; 433 | } 434 | write_foreground(index_pin, wrgate_pin, (uint8_t *)pulses, 435 | (uint8_t *)pulse_end, store_greaseweazle, use_index); 436 | free_write(); 437 | return true; 438 | } 439 | 440 | #endif 441 | #endif 442 | -------------------------------------------------------------------------------- /src/arch_rp2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #if defined(__cplusplus) 3 | extern "C" { 4 | #endif 5 | 6 | #if defined(ARDUINO_ARCH_RP2040) 7 | #define read_index_fast() gpio_get(_indexpin) 8 | #define read_data() gpio_get(_rddatapin) 9 | #define set_debug_led() gpio_put(led_pin, 1) 10 | #define clr_debug_led() gpio_put(led_pin, 0) 11 | #define set_write() gpio_put(_wrdatapin, 1) 12 | #define clr_write() gpio_put(_wrdatapin, 0) 13 | #include 14 | extern uint32_t 15 | rp2040_flux_capture(int indexpin, int rdpin, volatile uint8_t *pulses, 16 | volatile uint8_t *end, int32_t *falling_index_offset, 17 | bool store_greaseweazle, uint32_t capture_counts, 18 | uint32_t index_wait_ms); 19 | extern bool rp2040_flux_write(int index_pin, int wrgate_pin, int wrdata_pin, 20 | uint8_t *pulses, uint8_t *pulse_end, 21 | bool store_greaseweazel, bool is_apple2, 22 | bool use_index); 23 | #endif 24 | 25 | #if defined(__cplusplus) 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /src/arch_samd51.cpp: -------------------------------------------------------------------------------- 1 | #if defined(__SAMD51__) 2 | #include 3 | #include // pinPeripheral() func 4 | 5 | static const struct { 6 | Tc *tc; // -> Timer/Counter base address 7 | IRQn_Type IRQn; // Interrupt number 8 | int gclk; // GCLK ID 9 | int evu; // EVSYS user ID 10 | } tcList[] = {{TC0, TC0_IRQn, TC0_GCLK_ID, EVSYS_ID_USER_TC0_EVU}, 11 | {TC1, TC1_IRQn, TC1_GCLK_ID, EVSYS_ID_USER_TC1_EVU}, 12 | {TC2, TC2_IRQn, TC2_GCLK_ID, EVSYS_ID_USER_TC2_EVU}, 13 | {TC3, TC3_IRQn, TC3_GCLK_ID, EVSYS_ID_USER_TC3_EVU}, 14 | #ifdef TC4 15 | {TC4, TC4_IRQn, TC4_GCLK_ID, EVSYS_ID_USER_TC4_EVU}, 16 | #endif 17 | #ifdef TC5 18 | {TC5, TC5_IRQn, TC5_GCLK_ID, EVSYS_ID_USER_TC5_EVU}, 19 | #endif 20 | #ifdef TC6 21 | {TC6, TC6_IRQn, TC6_GCLK_ID, EVSYS_ID_USER_TC6_EVU}, 22 | #endif 23 | #ifdef TC7 24 | {TC7, TC7_IRQn, TC7_GCLK_ID, EVSYS_ID_USER_TC7_EVU} 25 | #endif 26 | }; 27 | 28 | Tc *theReadTimer = NULL; 29 | Tc *theWriteTimer = NULL; 30 | 31 | int g_cap_tc_num; 32 | volatile uint8_t *g_flux_pulses = NULL; 33 | volatile uint32_t g_max_pulses = 0; 34 | volatile uint32_t g_n_pulses = 0; 35 | volatile bool g_store_greaseweazle = false; 36 | volatile uint8_t g_timing_div = 2; 37 | volatile bool g_writing_pulses = false; 38 | 39 | void FLOPPY_TC_HANDLER() // Interrupt Service Routine (ISR) for timer TCx 40 | { 41 | // Check for match counter 0 (MC0) interrupt 42 | if (theReadTimer && theReadTimer->COUNT16.INTFLAG.bit.MC0) { 43 | uint16_t ticks = 44 | theReadTimer->COUNT16.CC[0].reg / g_timing_div; // Copy the period 45 | if (ticks == 0) { 46 | // dont do something if its 0 - thats wierd! 47 | } else if (ticks < 250 || !g_store_greaseweazle) { 48 | // 1-249: One byte. 49 | g_flux_pulses[g_n_pulses++] = min(249, ticks); 50 | } else { 51 | uint8_t high = (ticks - 250) / 255; 52 | if (high < 5) { 53 | // 250-1524: Two bytes. 54 | g_flux_pulses[g_n_pulses++] = 250 + high; 55 | g_flux_pulses[g_n_pulses++] = 1 + ((ticks - 250) % 255); 56 | } else { 57 | // TODO MEME FIX! 58 | /* 1525-(2^28-1): Seven bytes. 59 | g_flux_pulses[g_n_pulses++] = 0xff; 60 | g_flux_pulses[g_n_pulses++] = FLUXOP_SPACE; 61 | _write_28bit(ticks - 249); 62 | u_buf[U_MASK(u_prod++)] = 249; 63 | } 64 | */ 65 | } 66 | } 67 | } 68 | 69 | // Check for match counter 1 (MC1) interrupt 70 | if (theReadTimer && theReadTimer->COUNT16.INTFLAG.bit.MC1) { 71 | uint16_t pulsewidth = 72 | theReadTimer->COUNT16.CC[1].reg; // Copy the pulse width, DONT REMOVE 73 | (void)pulsewidth; 74 | } 75 | 76 | if (theWriteTimer && theWriteTimer->COUNT16.INTFLAG.bit.MC0) { 77 | // Because this uses CCBUF registers (updating CC on next timer rollover), 78 | // the pulse_index check here looks odd, checking both under AND over 79 | // num_pulses This is normal and OK and intended, because of the 80 | // last-pulse-out case where we need one extra invocation of the interrupt 81 | // to allow that pulse out before resetting PWM to steady high and disabling 82 | // the interrupt. 83 | if (g_n_pulses < g_max_pulses) { 84 | // Set period for next pulse 85 | 86 | uint16_t ticks = g_flux_pulses[g_n_pulses]; 87 | if (ticks == 0) { 88 | // dont do something if its 0 - thats wierd! 89 | } else if (ticks < 250 || !g_store_greaseweazle) { 90 | // 1-249: One byte. 91 | ticks = min(249, ticks); 92 | } else { 93 | // 250-1524: Two bytes. 94 | uint16_t high = ((ticks - 250) + 1) * 255; 95 | g_n_pulses++; 96 | ticks = high + g_flux_pulses[g_n_pulses]; 97 | } 98 | theWriteTimer->COUNT16.CCBUF[0].reg = ticks; 99 | } else if (g_n_pulses > g_max_pulses) { 100 | // Last pulse out was allowed its one extra PWM cycle, done now 101 | theWriteTimer->COUNT16.CCBUF[1].reg = 0; // Steady high on next pulse 102 | theWriteTimer->COUNT16.INTENCLR.bit.MC0 = 1; // Disable interrupt 103 | g_writing_pulses = false; 104 | } 105 | g_n_pulses++; // Outside if/else to allow last-pulse case 106 | theWriteTimer->COUNT16.INTFLAG.bit.MC0 = 1; // Clear interrupt flag 107 | } 108 | } 109 | 110 | // this isnt great but how else can we dynamically choose the pin? :/ 111 | void TC0_Handler() { FLOPPY_TC_HANDLER(); } 112 | void TC1_Handler() { FLOPPY_TC_HANDLER(); } 113 | void TC2_Handler() { FLOPPY_TC_HANDLER(); } 114 | void TC3_Handler() { FLOPPY_TC_HANDLER(); } 115 | void TC4_Handler() { FLOPPY_TC_HANDLER(); } 116 | 117 | /************************************************************************/ 118 | 119 | static bool init_capture_timer(int _rddatapin, Stream *debug_serial) { 120 | MCLK->APBBMASK.reg |= 121 | MCLK_APBBMASK_EVSYS; // Switch on the event system peripheral 122 | 123 | // Enable the port multiplexer on READDATA 124 | PinDescription pinDesc = g_APinDescription[_rddatapin]; 125 | uint32_t capture_port = pinDesc.ulPort; 126 | uint32_t capture_pin = pinDesc.ulPin; 127 | EExt_Interrupts capture_irq = pinDesc.ulExtInt; 128 | if (capture_irq == NOT_AN_INTERRUPT) { 129 | if (debug_serial) 130 | debug_serial->println("Not an interrupt pin!"); 131 | return false; 132 | } 133 | 134 | uint32_t tcNum = GetTCNumber(pinDesc.ulPWMChannel); 135 | uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel); 136 | 137 | if (tcNum < TCC_INST_NUM) { 138 | if (pinDesc.ulTCChannel != NOT_ON_TIMER) { 139 | if (debug_serial) 140 | debug_serial->println( 141 | "PWM is on a TCC not TC, lets look at the TCChannel"); 142 | tcNum = GetTCNumber(pinDesc.ulTCChannel); 143 | tcChannel = GetTCChannelNumber(pinDesc.ulTCChannel); 144 | } 145 | if (tcNum < TCC_INST_NUM) { 146 | if (debug_serial) 147 | debug_serial->println("Couldn't find a TC channel for this pin :("); 148 | return false; 149 | } 150 | } 151 | g_cap_tc_num = tcNum -= TCC_INST_NUM; // adjust naming 152 | if (debug_serial) 153 | debug_serial->printf("readdata on port %d and pin %d, IRQ #%d, TC%d.%d\n\r", 154 | capture_port, capture_pin, capture_irq, tcNum, 155 | tcChannel); 156 | theReadTimer = tcList[tcNum].tc; 157 | 158 | if (debug_serial) 159 | debug_serial->printf("TC GCLK ID=%d, EVU=%d\n\r", tcList[tcNum].gclk, 160 | tcList[tcNum].evu); 161 | 162 | // Setup INPUT capture clock 163 | 164 | GCLK->PCHCTRL[tcList[tcNum].gclk].reg = 165 | GCLK_PCHCTRL_GEN_GCLK1_Val | 166 | (1 << GCLK_PCHCTRL_CHEN_Pos); // use GCLK1 to get 48MHz on SAMD51 167 | PORT->Group[capture_port].PINCFG[capture_pin].bit.PMUXEN = 1; 168 | 169 | // Set-up the pin as an EIC (interrupt) peripheral on READDATA 170 | if (capture_pin % 2 == 0) { // even pmux 171 | PORT->Group[capture_port].PMUX[capture_pin >> 1].reg |= PORT_PMUX_PMUXE(0); 172 | } else { 173 | PORT->Group[capture_port].PMUX[capture_pin >> 1].reg |= PORT_PMUX_PMUXO(0); 174 | } 175 | 176 | EIC->CTRLA.bit.ENABLE = 0; // Disable the EIC peripheral 177 | while (EIC->SYNCBUSY.bit.ENABLE) 178 | ; // Wait for synchronization 179 | // Look for right CONFIG register to be addressed 180 | uint8_t eic_config, eic_config_pos; 181 | if (capture_irq > EXTERNAL_INT_7) { 182 | eic_config = 1; 183 | eic_config_pos = (capture_irq - 8) << 2; 184 | } else { 185 | eic_config = 0; 186 | eic_config_pos = capture_irq << 2; 187 | } 188 | 189 | // Set event on detecting a HIGH level 190 | EIC->CONFIG[eic_config].reg &= ~(EIC_CONFIG_SENSE0_Msk << eic_config_pos); 191 | EIC->CONFIG[eic_config].reg |= EIC_CONFIG_SENSE0_HIGH_Val << eic_config_pos; 192 | EIC->EVCTRL.reg = 193 | 1 << capture_irq; // Enable event output on external interrupt 194 | EIC->INTENCLR.reg = 1 << capture_irq; // Clear interrupt on external interrupt 195 | EIC->ASYNCH.reg = 1 << capture_irq; // Set-up interrupt as asynchronous input 196 | EIC->CTRLA.bit.ENABLE = 1; // Enable the EIC peripheral 197 | while (EIC->SYNCBUSY.bit.ENABLE) 198 | ; // Wait for synchronization 199 | 200 | // Select the event system user on channel 0 (USER number = channel number + 201 | // 1) 202 | EVSYS->USER[tcList[tcNum].evu].reg = 203 | EVSYS_USER_CHANNEL(1); // Set the event user (receiver) as timer 204 | 205 | // Select the event system generator on channel 0 206 | EVSYS->Channel[0].CHANNEL.reg = 207 | EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection 208 | EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous 209 | EVSYS_CHANNEL_EVGEN( 210 | EVSYS_ID_GEN_EIC_EXTINT_0 + 211 | capture_irq); // Set event generator (sender) as ext int 212 | 213 | theReadTimer->COUNT16.EVCTRL.reg = 214 | TC_EVCTRL_TCEI | // Enable the TCC event input 215 | // TC_EVCTRL_TCINV | // Invert the event 216 | // input 217 | TC_EVCTRL_EVACT_PPW; // Set up the timer for capture: CC0 period, CC1 218 | // pulsewidth 219 | 220 | NVIC_SetPriority(tcList[tcNum].IRQn, 221 | 0); // Set the Nested Vector Interrupt Controller (NVIC) 222 | // priority for TCx to 0 (highest) 223 | theReadTimer->COUNT16.INTENSET.reg = 224 | TC_INTENSET_MC1 | // Enable compare channel 1 (CC1) interrupts 225 | TC_INTENSET_MC0; // Enable compare channel 0 (CC0) interrupts 226 | 227 | theReadTimer->COUNT16.CTRLA.reg = 228 | TC_CTRLA_CAPTEN1 | // Enable pulse capture on CC1 229 | TC_CTRLA_CAPTEN0 | // Enable pulse capture on CC0 230 | // TC_CTRLA_PRESCSYNC_PRESC | // Roll over on prescaler clock 231 | // TC_CTRLA_PRESCALER_DIV1 | // Set the prescaler 232 | TC_CTRLA_MODE_COUNT16; // Set the timer to 16-bit mode 233 | 234 | return true; 235 | } 236 | 237 | static void enable_capture_timer(bool interrupt_driven) { 238 | if (!theReadTimer) 239 | return; 240 | 241 | if (interrupt_driven) { 242 | NVIC_EnableIRQ( 243 | tcList[g_cap_tc_num].IRQn); // Connect the TCx timer to the Nested 244 | // Vector Interrupt Controller (NVIC) 245 | } else { 246 | NVIC_DisableIRQ( 247 | tcList[g_cap_tc_num].IRQn); // Disconnect the TCx timer from the Nested 248 | // Vector Interrupt Controller (NVIC) 249 | } 250 | 251 | theReadTimer->COUNT16.CTRLA.bit.ENABLE = 1; // Enable the TC timer 252 | while (theReadTimer->COUNT16.SYNCBUSY.bit.ENABLE) 253 | ; // Wait for synchronization 254 | } 255 | 256 | static bool init_generate_timer(int _wrdatapin, Stream *debug_serial) { 257 | MCLK->APBBMASK.reg |= 258 | MCLK_APBBMASK_EVSYS; // Switch on the event system peripheral 259 | 260 | // Enable the port multiplexer on WRITEDATA 261 | PinDescription pinDesc = g_APinDescription[_wrdatapin]; 262 | uint32_t generate_port = pinDesc.ulPort; 263 | uint32_t generate_pin = pinDesc.ulPin; 264 | 265 | uint32_t tcNum = GetTCNumber(pinDesc.ulPWMChannel); 266 | uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel); 267 | 268 | if (tcNum < TCC_INST_NUM) { 269 | // OK so we're a TC! 270 | if (pinDesc.ulTCChannel != NOT_ON_TIMER) { 271 | if (debug_serial) 272 | debug_serial->println("PWM is on a TC"); 273 | tcNum = GetTCNumber(pinDesc.ulTCChannel); 274 | tcChannel = GetTCChannelNumber(pinDesc.ulTCChannel); 275 | 276 | pinPeripheral(_wrdatapin, PIO_TIMER); // PIO_TIMER if using a TC periph 277 | } 278 | if (tcNum < TCC_INST_NUM) { 279 | if (debug_serial) 280 | debug_serial->println("Couldn't find a TC channel for this pin :("); 281 | return false; 282 | } 283 | } else { 284 | pinPeripheral(_wrdatapin, 285 | PIO_TIMER_ALT); // PIO_TIMER_ALT if using a TCC periph 286 | } 287 | 288 | tcNum -= TCC_INST_NUM; // adjust naming 289 | if (debug_serial) 290 | debug_serial->printf("writedata on port %d and pin %d,, TC%d.%d\n\r", 291 | generate_port, generate_pin, tcNum, tcChannel); 292 | 293 | // Because of the PWM mode used, we MUST use a TC#/WO[1] pin, can't 294 | // use WO[0]. Different timers would need different pins, 295 | // but the WO[1] thing is NOT negotiable. 296 | if (tcChannel != 1) { 297 | debug_serial->println("Must be on TCx.1, but we're not!"); 298 | return false; 299 | } 300 | 301 | theWriteTimer = tcList[tcNum].tc; 302 | 303 | if (debug_serial) 304 | debug_serial->printf("TC GCLK ID=%d, EVU=%d\n\r", tcList[tcNum].gclk, 305 | tcList[tcNum].evu); 306 | 307 | // Configure TC timer source as GCLK1 (48 MHz peripheral clock) 308 | GCLK->PCHCTRL[tcList[tcNum].gclk].bit.CHEN = 0; 309 | while (GCLK->PCHCTRL[tcList[tcNum].gclk].bit.CHEN) 310 | ; // Wait for disable 311 | GCLK_PCHCTRL_Type pchctrl; 312 | pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK1_Val; // GCLK1 is 48 MHz 313 | pchctrl.bit.CHEN = 1; 314 | GCLK->PCHCTRL[tcList[tcNum].gclk].reg = pchctrl.reg; 315 | while (!GCLK->PCHCTRL[tcList[tcNum].gclk].bit.CHEN) 316 | ; // Wait for enable 317 | 318 | // Software reset timer/counter to default state (also disables it) 319 | theWriteTimer->COUNT16.CTRLA.bit.SWRST = 1; 320 | while (theWriteTimer->COUNT16.SYNCBUSY.bit.SWRST) 321 | ; 322 | 323 | // Configure for MPWM, 1:2 prescale (24 MHz). 16-bit mode is defailt. 324 | theWriteTimer->COUNT16.WAVE.bit.WAVEGEN = 3; // Match PWM mode 325 | theWriteTimer->COUNT16.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_DIV2_Val; 326 | // MPWM mode is weird but necessary because Normal PWM has a fixed TOP value. 327 | 328 | // Count-up, no one-shot is default state, no need to fiddle those bits. 329 | 330 | // Invert PWM channel 1 so it starts low, goes high on match 331 | theWriteTimer->COUNT16.DRVCTRL.bit.INVEN1 = 1; // INVEN1 = channel 1 332 | 333 | // Enable timer 334 | theWriteTimer->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE; 335 | while (theWriteTimer->COUNT16.SYNCBUSY.bit.ENABLE) 336 | ; 337 | 338 | // IRQ is enabled but match-compare interrupt isn't enabled 339 | // until send_pulses() is called. 340 | 341 | NVIC_DisableIRQ(tcList[tcNum].IRQn); 342 | NVIC_ClearPendingIRQ(tcList[tcNum].IRQn); 343 | NVIC_SetPriority(tcList[tcNum].IRQn, 0); // Top priority 344 | NVIC_EnableIRQ(tcList[tcNum].IRQn); 345 | 346 | return true; 347 | } 348 | 349 | static void enable_generate_timer(void) { 350 | theWriteTimer->COUNT16.COUNT.reg = 351 | 0; // Reset counter so we can time this right 352 | while (theWriteTimer->COUNT16.SYNCBUSY.bit.COUNT) 353 | ; 354 | // Trigger 1st pulse in ~1/4 uS (need moment to set up other registers) 355 | theWriteTimer->COUNT16.CC[0].reg = 5; 356 | while (theWriteTimer->COUNT16.SYNCBUSY.bit.CC0) 357 | ; 358 | // Set up duration of first pulse when COUNT rolls over 359 | theWriteTimer->COUNT16.CCBUF[0].reg = g_flux_pulses[0]; 360 | while (theWriteTimer->COUNT16.SYNCBUSY.bit.CC0) 361 | ; 362 | // Set up LOW period of pulses when COUNT rolls over 363 | theWriteTimer->COUNT16.CCBUF[1].reg = 5; // 0.25 uS low pulses 364 | while (theWriteTimer->COUNT16.SYNCBUSY.bit.CC1) 365 | ; 366 | // Enable match compare channel 0 interrupt 367 | theWriteTimer->COUNT16.INTENSET.bit.MC0 = 1; 368 | } 369 | 370 | #ifdef __cplusplus 371 | 372 | bool Adafruit_FloppyBase::init_capture(void) { 373 | return init_capture_timer(_rddatapin, debug_serial); 374 | } 375 | 376 | void Adafruit_FloppyBase::deinit_capture(void) { 377 | if (!theReadTimer) 378 | return; 379 | 380 | // Software reset timer/counter to default state (also disables it) 381 | theReadTimer->COUNT16.CTRLA.bit.SWRST = 1; 382 | while (theReadTimer->COUNT16.SYNCBUSY.bit.SWRST) 383 | ; 384 | theReadTimer = NULL; 385 | } 386 | 387 | void Adafruit_FloppyBase::enable_capture(void) { enable_capture_timer(true); } 388 | 389 | void Adafruit_FloppyBase::disable_capture(void) { 390 | if (!theReadTimer) 391 | return; 392 | 393 | theReadTimer->COUNT16.CTRLA.bit.ENABLE = 0; // disable the TC timer 394 | } 395 | 396 | bool Adafruit_FloppyBase::init_generate(void) { 397 | return init_generate_timer(_wrdatapin, debug_serial); 398 | } 399 | 400 | void Adafruit_FloppyBase::deinit_generate(void) { 401 | if (!theWriteTimer) 402 | return; 403 | 404 | // Software reset timer/counter to default state (also disables it) 405 | theWriteTimer->COUNT16.CTRLA.bit.SWRST = 1; 406 | while (theWriteTimer->COUNT16.SYNCBUSY.bit.SWRST) 407 | ; 408 | theWriteTimer = NULL; 409 | } 410 | 411 | void Adafruit_FloppyBase::enable_generate(void) { enable_generate_timer(); } 412 | 413 | void Adafruit_FloppyBase::disable_generate(void) { 414 | if (!theWriteTimer) 415 | return; 416 | 417 | theWriteTimer->COUNT16.CTRLA.bit.ENABLE = 0; // disable the TC timer 418 | } 419 | 420 | bool Adafruit_FloppyBase::start_polled_capture(void) { 421 | ::enable_capture_timer(false); 422 | return true; 423 | } 424 | 425 | uint16_t mfm_io_sample_flux(bool *index) { 426 | (void)index; 427 | 428 | if (!theReadTimer) { 429 | return ~(uint16_t)0; 430 | } 431 | 432 | // Check for match counter 0 (MC0) interrupt 433 | while (!(theReadTimer->COUNT16.INTFLAG.bit.MC0)) { 434 | /* NOTHING */ 435 | } 436 | uint16_t ticks = 437 | theReadTimer->COUNT16.CC[0].reg / g_timing_div; // Copy the period 438 | return ticks; 439 | } 440 | 441 | #endif 442 | 443 | #endif 444 | -------------------------------------------------------------------------------- /src/greasepack.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | // Encoding flux of duration T: 6 | // 0: Impossible 7 | // 1..249: Encodes as 1 byte (T itself) 8 | // 250..1524: Encodes as 2 bytes: (T // 255 + 250), ( 9 | // 1525..2^28: Encodes as 6 bytes: 255 + Space + "write_28bit" 10 | enum { cutoff_1byte = 250, cutoff_2byte = 1525, cutoff_6byte = (1 << 28) - 1 }; 11 | 12 | // Pack one flux duration into greaseaweazel format. 13 | // buf: Pointer to the current location in the flux buffer. NULL indicates no 14 | // buffer, regardless of end. end: Pointer to the end of the flux buffer value: 15 | // the flux value itself 16 | // 17 | // Returns: the new 'buf'. If buf==end, then the buffer is now full, and 18 | // the last byte is a terminating 0. This can also mean that the last value 19 | // was not stored because there was insufficient space, but there's no way to 20 | // tell apart an "exactly full" buffer from "the last sample didn't fit". 21 | static inline uint8_t *greasepack(uint8_t *buf, uint8_t *end, unsigned value) { 22 | // already no space left 23 | if (!buf || buf == end) { 24 | return buf; 25 | } 26 | 27 | size_t left = end - buf; 28 | size_t need = value < cutoff_1byte ? 1 : value < cutoff_2byte ? 2 : 6; 29 | 30 | // Buffer's going to be too full, store a terminating 0 and give up 31 | if (need > left) { 32 | *buf = 0; 33 | return end; 34 | } 35 | 36 | if (value < cutoff_1byte) { 37 | *buf++ = value; 38 | } else if (value < cutoff_2byte) { 39 | unsigned high = (value - 250) / 255; 40 | *buf++ = 250 + high; 41 | *buf++ = 1 + (value - 250) % 255; 42 | } else { 43 | if (value > cutoff_6byte) { 44 | value = cutoff_6byte; 45 | } 46 | *buf++ = 255; 47 | *buf++ = 2; 48 | *buf++ = 1 | ((value << 1) & 255); 49 | *buf++ = 1 | ((value >> 6) & 255); 50 | *buf++ = 1 | ((value >> 13) & 255); 51 | *buf++ = 1 | ((value >> 20) & 255); 52 | } 53 | 54 | return buf; 55 | } 56 | 57 | static inline unsigned greaseunpack(uint8_t **buf_, uint8_t *end, 58 | bool store_greaseweazel) { 59 | #define BUF (*buf_) 60 | if (!store_greaseweazel) { 61 | if (!BUF || BUF == end) { 62 | return 0xffff; 63 | } 64 | return *BUF++; 65 | } 66 | 67 | while (true) { 68 | // already no data left 69 | if (!BUF || BUF == end) { 70 | return 0xffff; 71 | } 72 | 73 | size_t left = end - BUF; 74 | uint8_t data = *BUF++; 75 | size_t need = data == 255 ? 6 : data >= cutoff_1byte ? 2 : 1; 76 | if (left < need) { 77 | BUF = end; 78 | return 0xffff; 79 | } 80 | 81 | if (need == 1) { 82 | return data; 83 | } 84 | if (need == 2) { 85 | uint8_t data2 = *BUF++; 86 | return (data - cutoff_1byte + 1) * 250 + data2; 87 | } 88 | uint8_t data2 = *BUF++; 89 | if (data2 != 2) { 90 | BUF += 4; 91 | continue; 92 | } // something other than FluxOp.Space 93 | uint32_t value = (*BUF++ & 254) >> 1; 94 | value += (*BUF++ & 254) << 6; 95 | value += (*BUF++ & 254) << 13; 96 | value += (*BUF++ & 254) << 20; 97 | 98 | return value; 99 | } 100 | } 101 | #undef BUF 102 | -------------------------------------------------------------------------------- /src/mfm_impl.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Jeff Epler for Adafruit Industries 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #if !defined(DEBUG_PRINTF) 13 | #define DEBUG_PRINTF(...) ((void)0) 14 | #endif 15 | 16 | #if !defined(DEBUG_ASSERT) 17 | #define DEBUG_ASSERT(x) assert(x) 18 | #endif 19 | 20 | /// @cond false 21 | 22 | #define MFM_MAYBE_UNUSED __attribute__((unused)) 23 | 24 | typedef struct mfm_io mfm_io_t; 25 | 26 | MFM_MAYBE_UNUSED 27 | static void mfm_io_encode_raw_mfm(mfm_io_t *io, uint8_t b); 28 | 29 | MFM_MAYBE_UNUSED 30 | static void mfm_io_encode_raw_fm(mfm_io_t *io, uint8_t b); 31 | 32 | typedef struct mfm_io_settings { 33 | uint16_t gap_1, gap_2; 34 | uint16_t gap_3[8]; // indexed by 'n', the sector size control 35 | uint16_t gap_4a; 36 | uint16_t gap_presync; 37 | uint8_t gap_byte; 38 | bool is_fm; 39 | } mfm_io_settings_t; 40 | 41 | static const mfm_io_settings_t standard_mfm = { 42 | 50, 22, {32, 54, 84, 116, 255, 255, 255, 255}, 80, 12, 0x4e, false, 43 | }; 44 | 45 | static const mfm_io_settings_t standard_fm = { 46 | 26, 11, {27, 42, 58, 138, 255, 255, 255, 255}, 40, 6, 0xff, true, 47 | }; 48 | 49 | struct mfm_io { 50 | bool encode_compact; ///< When writing flux, use compact form 51 | uint16_t T2_max; ///< MFM decoder max length of 2us pulse 52 | uint16_t T3_max; ///< MFM decoder max length of 3us pulse 53 | uint16_t T1_nom; ///< MFM nominal 1us pulse value 54 | 55 | size_t n_valid; ///< Count of valid sectors decoded 56 | 57 | uint8_t *pulses; ///< Encoded track data 58 | size_t n_pulses; ///< Total size of encoded track data 59 | size_t pos; ///< Position within encoded track data 60 | size_t time; ///< Total track time in flux units (set by encoder) 61 | 62 | uint8_t *sectors; ///< Pointer to decoded data 63 | size_t n_sectors; ///< Number of sectors on track 64 | 65 | uint8_t *sector_validity; ///< Which sectors decoded successfully 66 | uint8_t 67 | *cylinder_ptr; ///< When decoding, the cylinder number read is stored here 68 | uint8_t head, cylinder; ///< Location of the track on disk 69 | uint8_t pulse_len; ///< bookkeeping value used by MFM decoder 70 | uint8_t y; ///< bookkeeping value used by MFM encoder 71 | uint8_t n; ///< Sector size value. Sector is (128<pos >= io->n_pulses; } 113 | 114 | static mfm_io_symbol_t mfm_io_read_symbol(mfm_io_t *io) { 115 | if (mfm_io_eof(io)) { 116 | return mfm_io_pulse_10; 117 | } 118 | uint8_t pulse_len = io->pulses[io->pos++]; 119 | if (pulse_len > io->T3_max) 120 | return mfm_io_pulse_1000; 121 | if (pulse_len > io->T2_max) 122 | return mfm_io_pulse_100; 123 | return mfm_io_pulse_10; 124 | } 125 | 126 | // Automatically generated CRC function 127 | // polynomial: 0x11021 128 | static const uint16_t mfm_io_crc16_table[256] = { 129 | 0x0000U, 0x1021U, 0x2042U, 0x3063U, 0x4084U, 0x50A5U, 0x60C6U, 0x70E7U, 130 | 0x8108U, 0x9129U, 0xA14AU, 0xB16BU, 0xC18CU, 0xD1ADU, 0xE1CEU, 0xF1EFU, 131 | 0x1231U, 0x0210U, 0x3273U, 0x2252U, 0x52B5U, 0x4294U, 0x72F7U, 0x62D6U, 132 | 0x9339U, 0x8318U, 0xB37BU, 0xA35AU, 0xD3BDU, 0xC39CU, 0xF3FFU, 0xE3DEU, 133 | 0x2462U, 0x3443U, 0x0420U, 0x1401U, 0x64E6U, 0x74C7U, 0x44A4U, 0x5485U, 134 | 0xA56AU, 0xB54BU, 0x8528U, 0x9509U, 0xE5EEU, 0xF5CFU, 0xC5ACU, 0xD58DU, 135 | 0x3653U, 0x2672U, 0x1611U, 0x0630U, 0x76D7U, 0x66F6U, 0x5695U, 0x46B4U, 136 | 0xB75BU, 0xA77AU, 0x9719U, 0x8738U, 0xF7DFU, 0xE7FEU, 0xD79DU, 0xC7BCU, 137 | 0x48C4U, 0x58E5U, 0x6886U, 0x78A7U, 0x0840U, 0x1861U, 0x2802U, 0x3823U, 138 | 0xC9CCU, 0xD9EDU, 0xE98EU, 0xF9AFU, 0x8948U, 0x9969U, 0xA90AU, 0xB92BU, 139 | 0x5AF5U, 0x4AD4U, 0x7AB7U, 0x6A96U, 0x1A71U, 0x0A50U, 0x3A33U, 0x2A12U, 140 | 0xDBFDU, 0xCBDCU, 0xFBBFU, 0xEB9EU, 0x9B79U, 0x8B58U, 0xBB3BU, 0xAB1AU, 141 | 0x6CA6U, 0x7C87U, 0x4CE4U, 0x5CC5U, 0x2C22U, 0x3C03U, 0x0C60U, 0x1C41U, 142 | 0xEDAEU, 0xFD8FU, 0xCDECU, 0xDDCDU, 0xAD2AU, 0xBD0BU, 0x8D68U, 0x9D49U, 143 | 0x7E97U, 0x6EB6U, 0x5ED5U, 0x4EF4U, 0x3E13U, 0x2E32U, 0x1E51U, 0x0E70U, 144 | 0xFF9FU, 0xEFBEU, 0xDFDDU, 0xCFFCU, 0xBF1BU, 0xAF3AU, 0x9F59U, 0x8F78U, 145 | 0x9188U, 0x81A9U, 0xB1CAU, 0xA1EBU, 0xD10CU, 0xC12DU, 0xF14EU, 0xE16FU, 146 | 0x1080U, 0x00A1U, 0x30C2U, 0x20E3U, 0x5004U, 0x4025U, 0x7046U, 0x6067U, 147 | 0x83B9U, 0x9398U, 0xA3FBU, 0xB3DAU, 0xC33DU, 0xD31CU, 0xE37FU, 0xF35EU, 148 | 0x02B1U, 0x1290U, 0x22F3U, 0x32D2U, 0x4235U, 0x5214U, 0x6277U, 0x7256U, 149 | 0xB5EAU, 0xA5CBU, 0x95A8U, 0x8589U, 0xF56EU, 0xE54FU, 0xD52CU, 0xC50DU, 150 | 0x34E2U, 0x24C3U, 0x14A0U, 0x0481U, 0x7466U, 0x6447U, 0x5424U, 0x4405U, 151 | 0xA7DBU, 0xB7FAU, 0x8799U, 0x97B8U, 0xE75FU, 0xF77EU, 0xC71DU, 0xD73CU, 152 | 0x26D3U, 0x36F2U, 0x0691U, 0x16B0U, 0x6657U, 0x7676U, 0x4615U, 0x5634U, 153 | 0xD94CU, 0xC96DU, 0xF90EU, 0xE92FU, 0x99C8U, 0x89E9U, 0xB98AU, 0xA9ABU, 154 | 0x5844U, 0x4865U, 0x7806U, 0x6827U, 0x18C0U, 0x08E1U, 0x3882U, 0x28A3U, 155 | 0xCB7DU, 0xDB5CU, 0xEB3FU, 0xFB1EU, 0x8BF9U, 0x9BD8U, 0xABBBU, 0xBB9AU, 156 | 0x4A75U, 0x5A54U, 0x6A37U, 0x7A16U, 0x0AF1U, 0x1AD0U, 0x2AB3U, 0x3A92U, 157 | 0xFD2EU, 0xED0FU, 0xDD6CU, 0xCD4DU, 0xBDAAU, 0xAD8BU, 0x9DE8U, 0x8DC9U, 158 | 0x7C26U, 0x6C07U, 0x5C64U, 0x4C45U, 0x3CA2U, 0x2C83U, 0x1CE0U, 0x0CC1U, 159 | 0xEF1FU, 0xFF3EU, 0xCF5DU, 0xDF7CU, 0xAF9BU, 0xBFBAU, 0x8FD9U, 0x9FF8U, 160 | 0x6E17U, 0x7E36U, 0x4E55U, 0x5E74U, 0x2E93U, 0x3EB2U, 0x0ED1U, 0x1EF0U, 161 | }; 162 | 163 | static uint16_t mfm_io_crc16(const uint8_t *data, int len, uint16_t crc) { 164 | while (len > 0) { 165 | crc = mfm_io_crc16_table[*data ^ (uint8_t)(crc >> 8)] ^ (crc << 8); 166 | data++; 167 | len--; 168 | } 169 | return crc; 170 | } 171 | 172 | enum { 173 | mfm_io_triple_mark_magic = 0x09926499, 174 | mfm_io_triple_mark_mask = 0x0fffffff 175 | }; 176 | 177 | static bool skip_triple_sync_mark(mfm_io_t *io) { 178 | uint32_t state = 0; 179 | while (!mfm_io_eof(io) && state != mfm_io_triple_mark_magic) { 180 | state = ((state << 2) | mfm_io_read_symbol(io)) & mfm_io_triple_mark_mask; 181 | } 182 | DEBUG_PRINTF("mark @ %zd ? %d\n", io->pos, state == mfm_io_triple_mark_magic); 183 | return state == mfm_io_triple_mark_magic; 184 | } 185 | 186 | // The MFM crc initialization value, _excluding the three 0xa1 sync bytes_ 187 | enum { mfm_io_crc_preload_value = 0xcdb4 }; 188 | 189 | // Copy data into a series of buffers, returning the CRC. 190 | // This must be called right after sync_triple_sync_mark, because an assumption 191 | // is made about the code that's about to be read. 192 | // 193 | // The "..." arguments must be pairs of (uint8_t *buf, size_t n), ending with a 194 | // NULL buf. 195 | __attribute__((sentinel)) static uint16_t receive_crc(mfm_io_t *io, ...) { 196 | // `tmp` holds up to 9 bits of data, in bits 6..15. 197 | unsigned tmp = 0, weight = 0x8000; 198 | uint16_t crc = mfm_io_crc_preload_value; 199 | 200 | #define PUT_BIT(x) \ 201 | do { \ 202 | if (x) \ 203 | tmp |= weight; \ 204 | weight >>= 1; \ 205 | } while (0) 206 | 207 | // In MFM, flux marks can be 2, 3, or 4 "T" apart. These three signals 208 | // stand for the bit sequences 10, 100, and 1000. However, half of the 209 | // bits are data bits, and half are 'clock' bits. We have to keep track of 210 | // whether [in the next symbol] we want the "mfm_io_even" bit(s) or the 211 | // "mfm_io_odd" bit(s): 212 | // 213 | // 10 - leaves mfm_io_even/mfm_io_odd (parity) unchanged 214 | // 100 - inverts mfm_io_even/mfm_io_odd (parity) 215 | // 1000 - leaves mfm_io_even/mfm_io_odd (parity) unchanged 216 | // ^ ^ data bits if state is mfm_io_even 217 | // ^ ^ data bits if state is mfm_io_odd 218 | 219 | // We do this by knowing that when we arrive, we are waiting to parse the 220 | // final '1' data bit of the MFM sync mark. This means we apply a special rule 221 | // to the first word, starting as though in the 'mfm_io_even' state but not 222 | // recording the '1' bit. 223 | mfm_io_symbol_t s = mfm_io_read_symbol(io); 224 | mfm_state_t state = mfm_io_even; 225 | switch (s) { 226 | case mfm_io_pulse_100: // first data bit is a 0, and we start in the ODD state 227 | state = mfm_io_odd; 228 | /* fallthrough */ 229 | case mfm_io_pulse_1000: // first data bit is a 0, and we start in EVEN state 230 | PUT_BIT(0); 231 | break; 232 | default: // this flux doesn't represent a data bit, and we start in the EVEN 233 | // state 234 | break; 235 | } 236 | 237 | va_list ap; 238 | va_start(ap, io); 239 | uint8_t *buf; 240 | while ((buf = va_arg(ap, uint8_t *)) != NULL) { 241 | size_t n = va_arg(ap, size_t); 242 | while (n) { 243 | s = mfm_io_read_symbol(io); 244 | PUT_BIT( 245 | state); // 'mfm_io_even' is 1, so record a '1' or '0' as appropriate 246 | if (s == mfm_io_pulse_1000) { 247 | PUT_BIT(0); // the other bit recorded for a 1000 is always a '0' 248 | } 249 | if (s == mfm_io_pulse_100) { 250 | if (state) { 251 | PUT_BIT(0); 252 | } // If 'mfm_io_even', record an additional '0' 253 | state = (mfm_state_t)!state; // the next symbol has opposite parity 254 | } 255 | 256 | if (weight <= 0x80) { 257 | *buf = tmp >> 8; 258 | crc = mfm_io_crc16_table[*buf ^ (uint8_t)(crc >> 8)] ^ (crc << 8); 259 | tmp <<= 8; 260 | weight <<= 8; 261 | buf++; 262 | n--; 263 | } 264 | } 265 | } 266 | va_end(ap); 267 | return crc; 268 | } 269 | 270 | // Read a whole track, setting validity[] for each sector actually read, up to 271 | // n_sectors indexing of validity & data is 0-based, mfm_io_even though 272 | // MFM_IO_IDAMs store sectors as 1-based 273 | MFM_MAYBE_UNUSED 274 | static size_t decode_track_mfm(mfm_io_t *io) { 275 | io->pos = 0; 276 | 277 | // count previous valid sectors, so we can early-terminate if we're just 278 | // picking up some errored sectors on a 2nd pass 279 | io->n_valid = 0; 280 | for (size_t i = 0; i < io->n_sectors; i++) 281 | if (io->sector_validity[i]) 282 | io->n_valid += 1; 283 | 284 | uint8_t mark; 285 | uint8_t idam_buf[mfm_io_idam_size]; 286 | uint8_t crc_buf[mfm_io_crc_size]; 287 | 288 | // IDAM structure is: 289 | // * buf[0]: cylinder 290 | // * buf[1]: head 291 | // * buf[2]: sector 292 | // * buf[3]: "n" (sector size shift) -- must be 2 for 512 bytes 293 | // Only the sector number is validated. In theory, the other values should be 294 | // validated and we are only interested in working with DOS/Windows MFM 295 | // floppies which always use 512 byte sectors 296 | while (!mfm_io_eof(io) && io->n_valid < io->n_sectors) { 297 | if (!skip_triple_sync_mark(io)) { 298 | continue; 299 | } 300 | 301 | uint16_t crc = receive_crc(io, &mark, 1, idam_buf, sizeof(idam_buf), 302 | crc_buf, sizeof(crc_buf), NULL); 303 | 304 | DEBUG_PRINTF("mark=%02x [expecting IDAM=%02x]\n", mark, MFM_IO_IDAM); 305 | DEBUG_PRINTF("idam=%02x %02x %02x %02x\n", idam_buf[0], idam_buf[1], 306 | idam_buf[2], idam_buf[3]); 307 | DEBUG_PRINTF("crc_buf=%02x %02x\n", crc_buf[0], crc_buf[1]); 308 | DEBUG_PRINTF("crc=%04x [expecting 0]\n", crc); 309 | if (mark != MFM_IO_IDAM) { 310 | continue; 311 | } 312 | if (crc != 0) { 313 | continue; 314 | } 315 | 316 | // TODO: verify track & side numbers in IDAM 317 | size_t r = (uint8_t)idam_buf[2] - 1; // sectors are 1-based 318 | if (r >= io->n_sectors) { 319 | continue; 320 | } 321 | 322 | if (io->sector_validity[r]) { 323 | continue; 324 | } 325 | 326 | if (!skip_triple_sync_mark(io)) { 327 | continue; 328 | } 329 | size_t io_block_size = 128 << io->n; 330 | crc = receive_crc(io, &mark, 1, io->sectors + io_block_size * r, 331 | io_block_size, crc_buf, sizeof(crc_buf), NULL); 332 | DEBUG_PRINTF("mark=%02x [expecting DAM=%02x]\n", mark, MFM_IO_DAM); 333 | DEBUG_PRINTF("crc_buf=%02x %02x\n", crc_buf[0], crc_buf[1]); 334 | DEBUG_PRINTF("crc=%04x [expecting 0]\n", crc); 335 | if (mark != MFM_IO_DAM) { 336 | continue; 337 | } 338 | if (crc != 0) { 339 | continue; 340 | } 341 | 342 | if (io->cylinder_ptr) 343 | *io->cylinder_ptr = idam_buf[0]; 344 | io->sector_validity[r] = 1; 345 | io->n_valid++; 346 | } 347 | return io->n_valid; 348 | } 349 | 350 | static void mfm_io_flux_put(mfm_io_t *io, uint8_t len) { 351 | if (mfm_io_eof(io)) 352 | return; 353 | io->pulses[io->pos++] = len; 354 | } 355 | 356 | static void mfm_io_flux_byte_compact(mfm_io_t *io, uint8_t b) { 357 | if (mfm_io_eof(io)) 358 | return; 359 | io->pulses[io->pos++] = b; 360 | } 361 | 362 | static void mfm_io_flux_byte(mfm_io_t *io, uint8_t b) { 363 | for (int i = 8; i-- > 0;) { 364 | if (b & (1 << i)) { 365 | io->time += io->pulse_len + 1; 366 | mfm_io_flux_put(io, (1 + io->pulse_len) * io->T1_nom); 367 | io->pulse_len = 0; 368 | } else { 369 | io->pulse_len += 1; 370 | } 371 | } 372 | } 373 | 374 | static void mfm_io_encode_raw_fm(mfm_io_t *io, uint8_t b) { 375 | if ((b & 0xaa) == 0) { 376 | b |= 0xaa; 377 | } 378 | io->flux_byte(io, b); 379 | } 380 | 381 | static void mfm_io_encode_raw_mfm(mfm_io_t *io, uint8_t b) { 382 | uint16_t y = (io->y << 8) | b; 383 | if ((b & 0xaa) == 0) { 384 | // if there are no clocks, synthesize them 385 | y |= ~((y >> 1) | (y << 1)) & 0xaaaa; 386 | y &= 0xff; 387 | } 388 | io->flux_byte(io, y); 389 | io->y = y; 390 | } 391 | 392 | static const uint16_t mfm_encode_list[] = { 393 | // taken from greaseweazle 394 | 0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15, 0x40, 395 | 0x41, 0x44, 0x45, 0x50, 0x51, 0x54, 0x55, 0x100, 0x101, 396 | 0x104, 0x105, 0x110, 0x111, 0x114, 0x115, 0x140, 0x141, 0x144, 397 | 0x145, 0x150, 0x151, 0x154, 0x155, 0x400, 0x401, 0x404, 0x405, 398 | 0x410, 0x411, 0x414, 0x415, 0x440, 0x441, 0x444, 0x445, 0x450, 399 | 0x451, 0x454, 0x455, 0x500, 0x501, 0x504, 0x505, 0x510, 0x511, 400 | 0x514, 0x515, 0x540, 0x541, 0x544, 0x545, 0x550, 0x551, 0x554, 401 | 0x555, 0x1000, 0x1001, 0x1004, 0x1005, 0x1010, 0x1011, 0x1014, 0x1015, 402 | 0x1040, 0x1041, 0x1044, 0x1045, 0x1050, 0x1051, 0x1054, 0x1055, 0x1100, 403 | 0x1101, 0x1104, 0x1105, 0x1110, 0x1111, 0x1114, 0x1115, 0x1140, 0x1141, 404 | 0x1144, 0x1145, 0x1150, 0x1151, 0x1154, 0x1155, 0x1400, 0x1401, 0x1404, 405 | 0x1405, 0x1410, 0x1411, 0x1414, 0x1415, 0x1440, 0x1441, 0x1444, 0x1445, 406 | 0x1450, 0x1451, 0x1454, 0x1455, 0x1500, 0x1501, 0x1504, 0x1505, 0x1510, 407 | 0x1511, 0x1514, 0x1515, 0x1540, 0x1541, 0x1544, 0x1545, 0x1550, 0x1551, 408 | 0x1554, 0x1555, 0x4000, 0x4001, 0x4004, 0x4005, 0x4010, 0x4011, 0x4014, 409 | 0x4015, 0x4040, 0x4041, 0x4044, 0x4045, 0x4050, 0x4051, 0x4054, 0x4055, 410 | 0x4100, 0x4101, 0x4104, 0x4105, 0x4110, 0x4111, 0x4114, 0x4115, 0x4140, 411 | 0x4141, 0x4144, 0x4145, 0x4150, 0x4151, 0x4154, 0x4155, 0x4400, 0x4401, 412 | 0x4404, 0x4405, 0x4410, 0x4411, 0x4414, 0x4415, 0x4440, 0x4441, 0x4444, 413 | 0x4445, 0x4450, 0x4451, 0x4454, 0x4455, 0x4500, 0x4501, 0x4504, 0x4505, 414 | 0x4510, 0x4511, 0x4514, 0x4515, 0x4540, 0x4541, 0x4544, 0x4545, 0x4550, 415 | 0x4551, 0x4554, 0x4555, 0x5000, 0x5001, 0x5004, 0x5005, 0x5010, 0x5011, 416 | 0x5014, 0x5015, 0x5040, 0x5041, 0x5044, 0x5045, 0x5050, 0x5051, 0x5054, 417 | 0x5055, 0x5100, 0x5101, 0x5104, 0x5105, 0x5110, 0x5111, 0x5114, 0x5115, 418 | 0x5140, 0x5141, 0x5144, 0x5145, 0x5150, 0x5151, 0x5154, 0x5155, 0x5400, 419 | 0x5401, 0x5404, 0x5405, 0x5410, 0x5411, 0x5414, 0x5415, 0x5440, 0x5441, 420 | 0x5444, 0x5445, 0x5450, 0x5451, 0x5454, 0x5455, 0x5500, 0x5501, 0x5504, 421 | 0x5505, 0x5510, 0x5511, 0x5514, 0x5515, 0x5540, 0x5541, 0x5544, 0x5545, 422 | 0x5550, 0x5551, 0x5554, 0x5555}; 423 | 424 | static void mfm_io_encode_fm_sync(mfm_io_t *io, uint8_t data, uint8_t clock) { 425 | uint16_t encoded = 0; 426 | // can this be done with two lookups in encoded[] ? 427 | for (size_t i = 0; i < 8; i++) { 428 | encoded <<= 1; 429 | encoded |= (clock >> (7 - i)) & 1; 430 | encoded <<= 1; 431 | encoded |= (data >> (7 - i)) & 1; 432 | } 433 | io->encode_raw(io, encoded >> 8); 434 | io->encode_raw(io, encoded & 0xff); 435 | } 436 | 437 | static void mfm_io_encode_fm_sync_crc(mfm_io_t *io, uint8_t data, 438 | uint8_t clock) { 439 | mfm_io_encode_fm_sync(io, data, clock); 440 | io->crc = mfm_io_crc16(&data, 1, io->crc); 441 | } 442 | 443 | static void mfm_io_encode_byte(mfm_io_t *io, uint8_t b) { 444 | uint16_t encoded = mfm_encode_list[b]; 445 | io->encode_raw(io, encoded >> 8); 446 | io->encode_raw(io, encoded & 0xff); 447 | } 448 | 449 | static void mfm_io_encode_raw_buf(mfm_io_t *io, const uint8_t *buf, size_t n) { 450 | for (size_t i = 0; i < n; i++) { 451 | io->encode_raw(io, buf[i]); 452 | } 453 | } 454 | 455 | static void mfm_io_encode_gap(mfm_io_t *io, size_t n_gap) { 456 | for (size_t i = 0; i < n_gap; i++) { 457 | mfm_io_encode_byte(io, io->settings->gap_byte); 458 | } 459 | } 460 | 461 | static void mfm_io_encode_gap_and_presync(mfm_io_t *io, size_t n_gap) { 462 | mfm_io_encode_gap(io, n_gap); 463 | for (size_t i = 0; i < io->settings->gap_presync; i++) { 464 | mfm_io_encode_byte(io, 0); 465 | } 466 | } 467 | 468 | static void mfm_io_encode_gap_and_sync(mfm_io_t *io, size_t n_gap) { 469 | mfm_io_encode_gap_and_presync(io, n_gap); 470 | if (io->settings->is_fm) { 471 | mfm_io_encode_raw_buf(io, mfm_io_sync_bytes_fm, 472 | sizeof(mfm_io_sync_bytes_fm)); 473 | } else { 474 | mfm_io_encode_raw_buf(io, mfm_io_sync_bytes_mfm, 475 | sizeof(mfm_io_sync_bytes_mfm)); 476 | } 477 | } 478 | 479 | static void mfm_io_encode_iam(mfm_io_t *io) { 480 | mfm_io_encode_gap_and_presync(io, io->settings->gap_4a); 481 | if (io->settings->is_fm) { 482 | mfm_io_encode_raw_buf(io, mfm_io_iam_sync_bytes_fm, 483 | sizeof(mfm_io_iam_sync_bytes_fm)); 484 | } else { 485 | mfm_io_encode_raw_buf(io, mfm_io_iam_sync_bytes_mfm, 486 | sizeof(mfm_io_iam_sync_bytes_mfm)); 487 | } 488 | mfm_io_encode_byte(io, MFM_IO_IAM); 489 | } 490 | 491 | static void mfm_io_encode_buf(mfm_io_t *io, const uint8_t *buf, size_t n) { 492 | for (size_t i = 0; i < n; i++) { 493 | mfm_io_encode_byte(io, buf[i]); 494 | } 495 | } 496 | 497 | static void mfm_io_crc_preload(mfm_io_t *io) { 498 | if (io->settings->is_fm) { 499 | io->crc = 0xffff; 500 | } else { 501 | io->crc = mfm_io_crc_preload_value; 502 | } 503 | } 504 | 505 | static void mfm_io_encode_buf_crc(mfm_io_t *io, const uint8_t *buf, size_t n) { 506 | mfm_io_encode_buf(io, buf, n); 507 | io->crc = mfm_io_crc16(buf, n, io->crc); 508 | } 509 | 510 | static void mfm_io_encode_byte_crc(mfm_io_t *io, uint8_t b) { 511 | mfm_io_encode_buf_crc(io, &b, 1); 512 | } 513 | 514 | static void mfm_io_encode_crc(mfm_io_t *io) { 515 | unsigned crc = io->crc; 516 | mfm_io_encode_byte_crc(io, crc >> 8); 517 | mfm_io_encode_byte_crc(io, crc & 0xff); 518 | DEBUG_ASSERT(io->crc == 0); 519 | } 520 | 521 | // Convert a whole track into flux, up to n_sectors. indexing of data is 522 | // 0-based, mfm_io_even though MFM_IO_IDAMs store sectors as 1-based 523 | MFM_MAYBE_UNUSED 524 | static size_t encode_track_mfm(mfm_io_t *io) { 525 | io->pos = 0; 526 | io->pulse_len = 0; 527 | io->y = 0; 528 | io->time = 0; 529 | io->flux_byte = 530 | io->encode_compact ? mfm_io_flux_byte_compact : mfm_io_flux_byte; 531 | io->encode_raw = 532 | io->settings->is_fm ? mfm_io_encode_raw_fm : mfm_io_encode_raw_mfm; 533 | 534 | // sector_validity might end up reused for interleave? 535 | // memset(io->sector_validity, 0, io->n_sectors); 536 | 537 | unsigned char buf[mfm_io_idam_size + 1]; 538 | 539 | mfm_io_encode_iam(io); 540 | 541 | mfm_io_encode_gap_and_sync(io, io->settings->gap_1); 542 | for (size_t i = 0; i < io->n_sectors; i++) { 543 | buf[0] = MFM_IO_IDAM; 544 | buf[1] = io->cylinder; 545 | buf[2] = io->head; 546 | buf[3] = i + 1; // sectors are 1-based 547 | buf[4] = io->n; 548 | 549 | mfm_io_crc_preload(io); 550 | if (io->settings->is_fm) { 551 | mfm_io_encode_fm_sync_crc(io, buf[0], fm_default_sync_clk); 552 | mfm_io_encode_buf_crc(io, buf + 1, sizeof(buf) - 1); 553 | } else { 554 | mfm_io_encode_buf_crc(io, buf, sizeof(buf)); 555 | } 556 | mfm_io_encode_crc(io); 557 | 558 | mfm_io_encode_gap_and_sync(io, io->settings->gap_2); 559 | mfm_io_crc_preload(io); 560 | if (io->settings->is_fm) { 561 | mfm_io_encode_fm_sync_crc(io, MFM_IO_DAM, fm_default_sync_clk); 562 | } else { 563 | mfm_io_encode_byte_crc(io, MFM_IO_DAM); 564 | } 565 | size_t io_block_size = 128 << io->n; 566 | mfm_io_encode_buf_crc(io, &io->sectors[io_block_size * i], io_block_size); 567 | mfm_io_encode_crc(io); 568 | 569 | mfm_io_encode_gap_and_sync(io, io->settings->gap_3[io->n]); 570 | } 571 | size_t result = io->pos; 572 | DEBUG_ASSERT(!mfm_io_eof(io)); 573 | 574 | while (!mfm_io_eof(io)) { 575 | mfm_io_encode_byte(io, io->settings->gap_byte); 576 | } 577 | return result; 578 | } 579 | 580 | // Encoding sectors in MFM: 581 | // * Each sector is preceded by "gap" bytes with value "gapbyte" 582 | // * Then "gap_presync" '\0' bytes 583 | // * Then "sync_bytes" pattern 584 | // * Then the MFM_IO_IDAM & header data & CRC 585 | // * Then "gap_presync" '\0' bytes 586 | // * Then "sync_bytes" pattern 587 | // * Then the MFM_IO_DAM & sector data & CRC 588 | // The track is filled out to the set length with "gapbyte"s 589 | // ref: 590 | // https://github.com/keirf/greaseweazle/blob/2484a089d6a50bdbc9fb9a2117ca3968ab3aa2a8/scripts/greaseweazle/codec/ibm/mfm.py 591 | // https://retrocmp.de/hardware/kryoflux/track-mfm-format.htm 592 | /// @endcond 593 | --------------------------------------------------------------------------------