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