From d816a4f19de435a59070948aa2e860cbb31e0f39 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 4 Jan 2022 17:26:25 -0600 Subject: [PATCH] Add floppyio Initially enabled for samd51, this enables reading raw flux data as well as DOS/MFM formatted media. This is only the low-level code for reading & decoding flux pulses from a floppy drive. high level details will live in a Python library. adafruit-circuitpython-floppy will take care of details like stepping from track to track, etc. --- .gitmodules | 3 + lib/adafruit_floppy | 1 + .../atmel-samd/common-hal/floppyio/__init__.h | 30 ++++ ports/atmel-samd/mpconfigport.mk | 2 + py/circuitpy_defns.mk | 4 + py/circuitpy_mpconfig.mk | 3 + shared-bindings/floppyio/__init__.c | 128 ++++++++++++++++++ shared-bindings/floppyio/__init__.h | 32 +++++ shared-module/floppyio/__init__.c | 108 +++++++++++++++ 9 files changed, 311 insertions(+) create mode 160000 lib/adafruit_floppy create mode 100644 ports/atmel-samd/common-hal/floppyio/__init__.h create mode 100644 shared-bindings/floppyio/__init__.c create mode 100644 shared-bindings/floppyio/__init__.h create mode 100644 shared-module/floppyio/__init__.c diff --git a/.gitmodules b/.gitmodules index 9376daebdc..c6c6512813 100644 --- a/.gitmodules +++ b/.gitmodules @@ -199,3 +199,6 @@ url = https://github.com/raspberrypi/rpi-firmware.git branch = master shallow = true +[submodule "lib/adafruit_floppy"] + path = lib/adafruit_floppy + url = https://github.com/adafruit/Adafruit_Floppy diff --git a/lib/adafruit_floppy b/lib/adafruit_floppy new file mode 160000 index 0000000000..d5d34127db --- /dev/null +++ b/lib/adafruit_floppy @@ -0,0 +1 @@ +Subproject commit d5d34127db8167b7a6aca8045d27f6b9e3930c6d diff --git a/ports/atmel-samd/common-hal/floppyio/__init__.h b/ports/atmel-samd/common-hal/floppyio/__init__.h new file mode 100644 index 0000000000..693babb2ef --- /dev/null +++ b/ports/atmel-samd/common-hal/floppyio/__init__.h @@ -0,0 +1,30 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Jeff Epler for Adafruit Industries + * + * 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. + */ + +#pragma once + +// empirical-ish from Arduino @ 120MHz +#define FLOPPYIO_SAMPLERATE (14666667) diff --git a/ports/atmel-samd/mpconfigport.mk b/ports/atmel-samd/mpconfigport.mk index 9d238834fe..51024b7b67 100644 --- a/ports/atmel-samd/mpconfigport.mk +++ b/ports/atmel-samd/mpconfigport.mk @@ -87,6 +87,7 @@ CIRCUITPY_TOUCHIO_USE_NATIVE = 0 CIRCUITPY_ALARM ?= 1 CIRCUITPY_PS2IO ?= 1 CIRCUITPY_SAMD ?= 1 +CIRCUITPY_FLOPPYIO ?= 1 CIRCUITPY_FRAMEBUFFERIO ?= $(CIRCUITPY_FULL_BUILD) CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_FRAMEBUFFERIO) CIRCUITPY_WATCHDOG ?= 1 @@ -107,6 +108,7 @@ CIRCUITPY_TOUCHIO_USE_NATIVE = 0 CIRCUITPY_ALARM ?= 1 CIRCUITPY_PS2IO ?= 1 CIRCUITPY_SAMD ?= 1 +CIRCUITPY_FLOPPYIO ?= 1 CIRCUITPY_FRAMEBUFFERIO ?= $(CIRCUITPY_FULL_BUILD) CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_FRAMEBUFFERIO) diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 48a37be499..06399673a2 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -164,6 +164,9 @@ endif ifeq ($(CIRCUITPY_VECTORIO),1) SRC_PATTERNS += vectorio/% endif +ifeq ($(CIRCUITPY_FLOPPYIO),1) +SRC_PATTERNS += floppyio/% +endif ifeq ($(CIRCUITPY_FRAMEBUFFERIO),1) SRC_PATTERNS += framebufferio/% endif @@ -526,6 +529,7 @@ SRC_SHARED_MODULE_ALL = \ displayio/TileGrid.c \ displayio/area.c \ displayio/__init__.c \ + floppyio/__init__.c \ fontio/BuiltinFont.c \ fontio/__init__.c \ framebufferio/FramebufferDisplay.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 14852580e5..d0d33949ab 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -219,6 +219,9 @@ CFLAGS += -DCIRCUITPY_ESPIDF=$(CIRCUITPY_ESPIDF) CIRCUITPY__EVE ?= 0 CFLAGS += -DCIRCUITPY__EVE=$(CIRCUITPY__EVE) +CIRCUITPY_FLOPPYIO ?= 0 +CFLAGS += -DCIRCUITPY_FLOPPYIO=$(CIRCUITPY_FLOPPYIO) + CIRCUITPY_FREQUENCYIO ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_FREQUENCYIO=$(CIRCUITPY_FREQUENCYIO) diff --git a/shared-bindings/floppyio/__init__.c b/shared-bindings/floppyio/__init__.c new file mode 100644 index 0000000000..c2d1fbb21f --- /dev/null +++ b/shared-bindings/floppyio/__init__.c @@ -0,0 +1,128 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Jeff Epler for Adafruit Industries + * + * 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. + */ + +#include "shared-bindings/floppyio/__init__.h" +#include "shared-bindings/digitalio/DigitalInOut.h" +#include "common-hal/floppyio/__init__.h" + +#include + +#include "py/binary.h" +#include "py/enum.h" +#include "py/obj.h" +#include "py/runtime.h" + +//| def flux_readinto(buffer: WriteableBuffer, data: DigitalInOut, index: DigitalInOut) -> int: +//| """Read flux transition information into the buffer. +//| +//| The function returns when the buffer has filled, or when the index input +//| indicates that one full revolution of data has been recorded. Due to +//| technical limitations, this process may not be interruptible by +//| KeyboardInterrupt. +//| +//| :param buffer: Read data into this buffer. Each element represents the time between successive zero-to-one transitions. +//| :param data: Pin on which the flux data appears +//| :param index: Pin on which the index pulse appears +//| :return: The actual number of bytes of read +//| """ +//| ... +//| +STATIC mp_obj_t floppyio_flux_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_buffer, ARG_data, ARG_index }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_index, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_WRITE); + digitalio_digitalinout_obj_t *data = assert_digitalinout(args[ARG_data].u_obj); + digitalio_digitalinout_obj_t *index = assert_digitalinout(args[ARG_index].u_obj); + + return MP_OBJ_NEW_SMALL_INT(common_hal_floppyio_flux_readinto(bufinfo.buf, bufinfo.len, data, index)); +} +MP_DEFINE_CONST_FUN_OBJ_KW(floppyio_flux_readinto_obj, 0, floppyio_flux_readinto); + +//| def mfm_readinto(buffer: WriteableBuffer, data: DigitalInOut, index: DigitalInOut) -> int: +//| """Read mfm blocks into the buffer. +//| +//| The track is assumed to consist of 512-byte sectors. +//| +//| The function returns when all sectors have been successfully read, or +//| a number of index pulses have occurred. Due to technical limitations, this +//| process may not be interruptible by KeyboardInterrupt. +//| +//| :param buffer: Read data into this buffer. Must be a multiple of 512. +//| :param data: Pin on which the mfm data appears +//| :param index: Pin on which the index pulse appears +//| :return: The actual number of sectors read +//| """ +//| ... +//| +STATIC mp_obj_t floppyio_mfm_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_buffer, ARG_data, ARG_index }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_index, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_WRITE); + digitalio_digitalinout_obj_t *data = assert_digitalinout(args[ARG_data].u_obj); + digitalio_digitalinout_obj_t *index = assert_digitalinout(args[ARG_index].u_obj); + + if (bufinfo.len % 512 != 0) { + mp_raise_ValueError(translate("Buffer must be a multiple of 512 bytes")); + } + size_t n_sectors = bufinfo.len / 512; + return MP_OBJ_NEW_SMALL_INT(common_hal_floppyio_mfm_readinto(bufinfo.buf, n_sectors, data, index)); +} +MP_DEFINE_CONST_FUN_OBJ_KW(floppyio_mfm_readinto_obj, 0, floppyio_mfm_readinto); + +//| samplerate: int +//| """The approximate sample rate in Hz used by flux_readinto.""" +//| + +STATIC const mp_rom_map_elem_t floppyio_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_floppyio) }, + { MP_ROM_QSTR(MP_QSTR_flux_readinto), MP_ROM_PTR(&floppyio_flux_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_mfm_readinto), MP_ROM_PTR(&floppyio_mfm_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_samplerate), MP_ROM_INT(FLOPPYIO_SAMPLERATE) }, +}; +STATIC MP_DEFINE_CONST_DICT(floppyio_module_globals, floppyio_module_globals_table); + +const mp_obj_module_t floppyio_module = { + .base = {&mp_type_module }, + .globals = (mp_obj_dict_t *)&floppyio_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_floppyio, floppyio_module, CIRCUITPY_FLOPPYIO); diff --git a/shared-bindings/floppyio/__init__.h b/shared-bindings/floppyio/__init__.h new file mode 100644 index 0000000000..322bbe7d43 --- /dev/null +++ b/shared-bindings/floppyio/__init__.h @@ -0,0 +1,32 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Jeff Epler for Adafruit Industries + * + * 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. + */ + +#pragma once + +#include "common-hal/digitalio/DigitalInOut.h" + +int common_hal_floppyio_flux_readinto(void *buf, size_t len, digitalio_digitalinout_obj_t *data, digitalio_digitalinout_obj_t *index); +int common_hal_floppyio_mfm_readinto(void *buf, size_t n_sector, digitalio_digitalinout_obj_t *data, digitalio_digitalinout_obj_t *index); diff --git a/shared-module/floppyio/__init__.c b/shared-module/floppyio/__init__.c new file mode 100644 index 0000000000..c68b4d9ad2 --- /dev/null +++ b/shared-module/floppyio/__init__.c @@ -0,0 +1,108 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Jeff Epler for Adafruit Industries + * + * 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. + */ + +#include "hal_gpio.h" + +#include "py/runtime.h" + +#include "shared-bindings/time/__init__.h" +#include "shared-bindings/floppyio/__init__.h" +#include "common-hal/floppyio/__init__.h" +#include "shared-bindings/digitalio/DigitalInOut.h" + +#define T2_5 (FLOPPYIO_SAMPLERATE * 5 / 2 / 1000000) +#define T3_5 (FLOPPYIO_SAMPLERATE * 7 / 2 / 1000000) + +#define MFM_IO_MMIO (1) +#include "lib/adafruit_floppy/mfm_impl.h" + +__attribute__((optimize("O3"))) +int common_hal_floppyio_flux_readinto(void *buf, size_t len, digitalio_digitalinout_obj_t *data, digitalio_digitalinout_obj_t *index) { + uint32_t index_mask; + volatile uint32_t *index_port = common_hal_digitalio_digitalinout_get_reg(index, DIGITALINOUT_REG_READ, &index_mask); + + uint32_t data_mask; + volatile uint32_t *data_port = common_hal_digitalio_digitalinout_get_reg(data, DIGITALINOUT_REG_READ, &data_mask); + +#undef READ_INDEX +#undef READ_DATA +#define READ_INDEX() (!!(*index_port & index_mask)) +#define READ_DATA() (!!(*data_port & data_mask)) + + memset(buf, 0, len); + + uint8_t *pulses = buf, *pulses_ptr = pulses, *pulses_end = pulses + len; + + common_hal_mcu_disable_interrupts(); + + // wait for index pulse low + while (READ_INDEX()) { /* NOTHING */ + } + + + // if data line is low, wait till it rises + while (!READ_DATA()) { /* NOTHING */ + } + + // and then till it drops down + while (READ_DATA()) { /* NOTHING */ + } + + uint32_t index_state = 0; + while (pulses_ptr < pulses_end) { + index_state = (index_state << 1) | READ_INDEX(); + if ((index_state & 3) == 2) { // a zero-to-one transition + break; + } + + unsigned pulse_count = 3; + while (!READ_DATA()) { + pulse_count++; + } + + while (READ_DATA()) { + pulse_count++; + } + + *pulses_ptr++ = MIN(255, pulse_count); + } + common_hal_mcu_enable_interrupts(); + + return pulses_ptr - pulses; +} + +int common_hal_floppyio_mfm_readinto(void *buf, size_t n_sectors, digitalio_digitalinout_obj_t *data, digitalio_digitalinout_obj_t *index) { + mfm_io_t io; + io.index_port = common_hal_digitalio_digitalinout_get_reg(index, DIGITALINOUT_REG_READ, &io.index_mask); + io.data_port = common_hal_digitalio_digitalinout_get_reg(data, DIGITALINOUT_REG_READ, &io.data_mask); + + common_hal_mcu_disable_interrupts(); + uint8_t validity[n_sectors]; + int result = read_track(io, n_sectors, buf, validity); + common_hal_mcu_enable_interrupts(); + + return result; +}