Kaynağa Gözat

Merge z/main

This merge unintentionally lost history. Below is the commits that were
accidentally squashed. For details see #9.

commit 2bd312ecd9ae664eed2c846c4797a552e031695c
Author: Alex Perez <aperez@rabbithole.ws>
Date:   Wed Dec 21 10:15:30 2022 -0800

    Bump firmware version to 1.2.0 - release prep

 src/ZuluSCSI_config.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

commit 2e4a5fcd00333fbeb77651658d69cc5bc3a1294c
Merge: b20a6f88 111abe5e
Author: Alex Perez <aperez@rabbithole.ws>
Date:   Wed Dec 21 08:46:17 2022 -0800

    Merge pull request #126 from ZuluSCSI/rp2040_perf_improvements

    Rp2040 write performance improvements and bug fixes

commit 111abe5ea5ca4f9e30d324ecca6d05e4fb6bb355
Author: Petteri Aimonen <jpa@git.mail.kapsi.fi>
Date:   Wed Dec 21 13:30:46 2022 +0200

    Add speed testing script

 utils/speed_tester.py | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 85 insertions(+)

commit 3d1ac5bb8a9c577db88d42fdd902f42d2e01c966
Author: Petteri Aimonen <jpa@git.mail.kapsi.fi>
Date:   Thu Dec 15 13:41:27 2022 +0200

    RP2040: Implement non-blocking read from SCSI bus

    This allows SCSI write commands to transfer data continuously on RP2040
    while SD card writes proceed.

    Now achieves 8 MB/s read & 6 MB/s write in 10 MHz sync mode.

 lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp |  47 ++
 lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.h   |  10 +-
 lib/ZuluSCSI_platform_RP2040/scsiPhy.cpp           |  24 +-
 lib/ZuluSCSI_platform_RP2040/scsiPhy.h             |   7 +
 lib/ZuluSCSI_platform_RP2040/scsi_accel.pio        |  55 +-
 lib/ZuluSCSI_platform_RP2040/scsi_accel.pio.h      |  90 ++-
 lib/ZuluSCSI_platform_RP2040/scsi_accel_rp2040.cpp | 615 ++++++++++++++++-----
 lib/ZuluSCSI_platform_RP2040/scsi_accel_rp2040.h   |  34 +-
 src/ZuluSCSI_disk.cpp                              | 188 +++++--
 9 files changed, 843 insertions(+), 227 deletions(-)

commit 77d20813e5944aea189edadcf7d276b288a385e2
Author: Petteri Aimonen <jpa@git.mail.kapsi.fi>
Date:   Mon Dec 12 14:50:32 2022 +0200

    RP2040: Use PIO for generating the parity bits

    Instead of software, use a combination of DMA and PIO hardware
    to access the parity lookup table. This removes the refill_dmabuf()
    bottleneck and frees up the second core.

 lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp |   8 +-
 lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.h   |   2 +-
 lib/ZuluSCSI_platform_RP2040/rp2040_sdio.cpp       |   4 +-
 lib/ZuluSCSI_platform_RP2040/scsi_accel.pio        |   9 +
 lib/ZuluSCSI_platform_RP2040/scsi_accel.pio.h      |  30 ++
 lib/ZuluSCSI_platform_RP2040/scsi_accel_rp2040.cpp | 440 +++++++++------------
 6 files changed, 238 insertions(+), 255 deletions(-)

commit b20a6f886c1ffd79c7779c4615083615ed152c29
Author: Alex Perez <aperez@rabbithole.ws>
Date:   Tue Dec 20 14:48:59 2022 -0800

    Update ZuluSCSI_config.h

 src/ZuluSCSI_config.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

commit 0f43d1d12c88f4bd8c72e08887ed7837b09f1a40
Merge: 5eef6c98 bf9be95a
Author: Alex Perez <aperez@rabbithole.ws>
Date:   Tue Dec 20 14:48:30 2022 -0800

    Merge pull request #125 from ZuluSCSI/disable-status-led

    Add the ability to disable the status LED via DisableStatusLED = 1 in zuluscsi.ini

commit bf9be95a043dee699a755b8675fbde837578fec6
Author: Morio <morio.earth@gmail.com>
Date:   Tue Dec 20 13:35:16 2022 -0800

    Add the ability to disable the status LED

    The LED is only disabled after the SD card is read.

    That means it will blink on boot and blink until it finds
    an SD card that is readable with a the zuluscsi.ini file and the
    "DisableStatusLED = 1" under "[SCSI]" is set. After which it will
    never blink again until the board is power cycled without
    "DisableStatusLED = 1" flag.

 lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.cpp | 6 ++++++
 lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.h   | 3 +++
 lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp   | 7 +++++++
 lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.h     | 3 +++
 lib/ZuluSCSI_platform_template/ZuluSCSI_platform.cpp | 8 ++++++++
 lib/ZuluSCSI_platform_template/ZuluSCSI_platform.h   | 3 +++
 src/ZuluSCSI.cpp                                     | 4 ++++
 zuluscsi.ini                                         | 1 +
 8 files changed, 35 insertions(+)

commit 6c0b08cf55d47095c47c34fb9994802305ccb80d
Author: Petteri Aimonen <jpa@git.mail.kapsi.fi>
Date:   Mon Dec 12 13:40:14 2022 +0200

    RP2040: Make accelerated SCSI routines work for odd number of bytes

    Primarily this fixes issues with synchronous mode transfers of
    ModeSense data. Previous fix in #89 (commit f11c0d4750) was a
    workaround that didn't fully implement the sync mode behavior.

    This commit unifies the behavior so that all writes and read go
    through the same code path.

    As-is, this commit causes a small performance degradation for
    transfer rates above 7 MB/s.
    It will be optimized in a following commit.

 lib/ZuluSCSI_platform_RP2040/scsiPhy.cpp           | 34 ++------------------
 lib/ZuluSCSI_platform_RP2040/scsi_accel.pio        | 14 ++++-----
 lib/ZuluSCSI_platform_RP2040/scsi_accel.pio.h      | 19 ++++++------
 lib/ZuluSCSI_platform_RP2040/scsi_accel_rp2040.cpp | 36 +++++++++-------------
 4 files changed, 33 insertions(+), 70 deletions(-)

commit 5eef6c988c6e2e3d811e390654f31daa148fe359
Merge: 511e79f8 747c4308
Author: Alex Perez <aperez@rabbithole.ws>
Date:   Wed Dec 7 09:41:05 2022 -0800

    Merge pull request #118 from ZuluSCSI/gpt_partition_support

    Add GPT partition support

commit 747c4308e18486ab719a38fcf03d2856a02fc445
Author: Petteri Aimonen <jpa@git.mail.kapsi.fi>
Date:   Wed Dec 7 15:30:18 2022 +0200

    SdFat: Update to version with GPT support

 .../src/ExFatLib/ExFatPartition.cpp                |  20 +---
 lib/SdFat_NoArduino/src/FatLib/FatPartition.cpp    |  20 +---
 lib/SdFat_NoArduino/src/common/FsStructs.h         |  30 ++++++
 lib/SdFat_NoArduino/src/common/PartitionTable.cpp  | 103 +++++++++++++++++++++
 lib/SdFat_NoArduino/src/common/PartitionTable.h    |  40 ++++++++
 platformio.ini                                     |   4 +-
 6 files changed, 185 insertions(+), 32 deletions(-)

commit c0ced33d3250713d2f1770c5e4afbb35426994e7
Author: Petteri Aimonen <jpa@git.mail.kapsi.fi>
Date:   Wed Dec 7 14:05:41 2022 +0200

    RP2040: Fix hang in uart code for early log messages

    RP2040 uart log prints hanged if they happened before uart_init() was
    called. Change it so that prints go to file but not serial in this
    case.

    Related to commit f89fd5292

 lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

commit d3b3c4b4d56bb02b8e37433b6d3b4643d3217891
Author: Petteri Aimonen <jpa@git.mail.kapsi.fi>
Date:   Wed Dec 7 14:05:22 2022 +0200

    Update code to work with SdFat 2.2.0

 lib/ZuluSCSI_platform_GD32F205/sd_card_sdio.cpp | 12 +++++++++++-
 lib/ZuluSCSI_platform_RP2040/sd_card_sdio.cpp   | 14 ++++++++++++--
 platformio.ini                                  |  4 ++--
 src/ZuluSCSI.cpp                                |  9 ++-------
 src/ZuluSCSI_disk.cpp                           |  2 +-
 5 files changed, 28 insertions(+), 13 deletions(-)

commit c23e8f9ba06b470a1c0e51ede4d97303da720ae6
Author: Petteri Aimonen <jpa@git.mail.kapsi.fi>
Date:   Wed Dec 7 14:02:31 2022 +0200

    Upgrade SdFat_NoArduino to match upstream 2.2.0

 lib/SdFat_NoArduino/doc/SdErrorCodes.txt           |  45 +-
 lib/SdFat_NoArduino/doc/mainpage.h                 |  24 +-
 .../examples/AvrAdcLogger/AvrAdcLogger.ino         |   1 +
 lib/SdFat_NoArduino/examples/SdInfo/SdInfo.ino     |  94 ++--
 lib/SdFat_NoArduino/examples/bench/bench.ino       |  14 +-
 .../DirectoryFunctions/DirectoryFunctions.ino      | 129 -----
 .../examples/examplesV1/OpenNext/OpenNext.ino      |  60 ---
 .../examples/examplesV1/QuickStart/QuickStart.ino  | 161 ------
 .../examplesV1/SdFormatter/SdFormatter.ino         | 552 ---------------------
 .../examples/examplesV1/SdInfo/SdInfo.ino          | 248 ---------
 .../examplesV1/SoftwareSpi/SoftwareSpi.ino         |  59 ---
 .../examplesV1/TeensySdioDemo/TeensySdioDemo.ino   | 169 -------
 .../examples/examplesV1/bench/bench.ino            | 222 ---------
 .../examples/examplesV1/rename/rename.ino          | 106 ----
 lib/SdFat_NoArduino/library.properties             |   2 +-
 lib/SdFat_NoArduino/src/BufferedPrint.h            |   2 +-
 lib/SdFat_NoArduino/src/ExFatLib/ExFatConfig.h     |   2 +-
 lib/SdFat_NoArduino/src/ExFatLib/ExFatDbg.cpp      |   6 +-
 lib/SdFat_NoArduino/src/ExFatLib/ExFatFile.cpp     |  56 ++-
 lib/SdFat_NoArduino/src/ExFatLib/ExFatFile.h       |  71 ++-
 .../src/ExFatLib/ExFatFilePrint.cpp                |   2 +-
 .../src/ExFatLib/ExFatFileWrite.cpp                |   8 +-
 .../src/ExFatLib/ExFatFormatter.cpp                |   2 +-
 lib/SdFat_NoArduino/src/ExFatLib/ExFatFormatter.h  |   2 +-
 lib/SdFat_NoArduino/src/ExFatLib/ExFatLib.h        |   2 +-
 lib/SdFat_NoArduino/src/ExFatLib/ExFatName.cpp     |   2 +-
 .../src/ExFatLib/ExFatPartition.cpp                |  44 +-
 lib/SdFat_NoArduino/src/ExFatLib/ExFatPartition.h  |  11 +-
 lib/SdFat_NoArduino/src/ExFatLib/ExFatVolume.cpp   |   2 +-
 lib/SdFat_NoArduino/src/ExFatLib/ExFatVolume.h     |  29 +-
 lib/SdFat_NoArduino/src/FatLib/FatDbg.cpp          |   6 +-
 lib/SdFat_NoArduino/src/FatLib/FatFile.cpp         |  64 ++-
 lib/SdFat_NoArduino/src/FatLib/FatFile.h           |  66 ++-
 lib/SdFat_NoArduino/src/FatLib/FatFileLFN.cpp      |   9 +-
 lib/SdFat_NoArduino/src/FatLib/FatFilePrint.cpp    |   2 +-
 lib/SdFat_NoArduino/src/FatLib/FatFileSFN.cpp      |  20 +-
 lib/SdFat_NoArduino/src/FatLib/FatFormatter.cpp    |   2 +-
 lib/SdFat_NoArduino/src/FatLib/FatFormatter.h      |   2 +-
 lib/SdFat_NoArduino/src/FatLib/FatLib.h            |   2 +-
 lib/SdFat_NoArduino/src/FatLib/FatName.cpp         |   2 +-
 lib/SdFat_NoArduino/src/FatLib/FatPartition.cpp    |  27 +-
 lib/SdFat_NoArduino/src/FatLib/FatPartition.h      |  12 +-
 lib/SdFat_NoArduino/src/FatLib/FatVolume.cpp       |   2 +-
 lib/SdFat_NoArduino/src/FatLib/FatVolume.h         |  27 +-
 lib/SdFat_NoArduino/src/FreeStack.cpp              |   2 +-
 lib/SdFat_NoArduino/src/FreeStack.h                |   2 +-
 lib/SdFat_NoArduino/src/FsLib/FsFile.cpp           |  35 +-
 lib/SdFat_NoArduino/src/FsLib/FsFile.h             |  65 ++-
 lib/SdFat_NoArduino/src/FsLib/FsFormatter.h        |   2 +-
 lib/SdFat_NoArduino/src/FsLib/FsLib.h              |   2 +-
 lib/SdFat_NoArduino/src/FsLib/FsNew.cpp            |   2 +-
 lib/SdFat_NoArduino/src/FsLib/FsNew.h              |   2 +-
 lib/SdFat_NoArduino/src/FsLib/FsVolume.cpp         |  10 +-
 lib/SdFat_NoArduino/src/FsLib/FsVolume.h           |  32 +-
 lib/SdFat_NoArduino/src/MinimumSerial.cpp          |   2 +-
 lib/SdFat_NoArduino/src/MinimumSerial.h            |   2 +-
 lib/SdFat_NoArduino/src/RingBuf.h                  |   2 +-
 lib/SdFat_NoArduino/src/SdCard/SdCard.h            |   2 +-
 lib/SdFat_NoArduino/src/SdCard/SdCardInfo.cpp      |   2 +-
 lib/SdFat_NoArduino/src/SdCard/SdCardInfo.h        | 324 +++++-------
 lib/SdFat_NoArduino/src/SdCard/SdCardInterface.h   |  15 +-
 lib/SdFat_NoArduino/src/SdCard/SdSpiCard.cpp       |  76 ++-
 lib/SdFat_NoArduino/src/SdCard/SdSpiCard.h         |  42 +-
 lib/SdFat_NoArduino/src/SdCard/SdioCard.h          |  15 +-
 lib/SdFat_NoArduino/src/SdCard/SdioTeensy.cpp      |  58 ++-
 lib/SdFat_NoArduino/src/SdFat.h                    |  10 +-
 lib/SdFat_NoArduino/src/SdFatConfig.h              |   5 +-
 .../src/SpiDriver/SdSpiArduinoDriver.h             |   2 +-
 lib/SdFat_NoArduino/src/SpiDriver/SdSpiArtemis.cpp |   2 +-
 lib/SdFat_NoArduino/src/SpiDriver/SdSpiAvr.h       |   2 +-
 .../src/SpiDriver/SdSpiBareUnoDriver.h             |   2 +-
 lib/SdFat_NoArduino/src/SpiDriver/SdSpiBaseClass.h |   2 +-
 .../src/SpiDriver/SdSpiChipSelect.cpp              |   2 +-
 lib/SdFat_NoArduino/src/SpiDriver/SdSpiDriver.h    |   2 +-
 lib/SdFat_NoArduino/src/SpiDriver/SdSpiDue.cpp     |   2 +-
 lib/SdFat_NoArduino/src/SpiDriver/SdSpiESP.cpp     |   2 +-
 lib/SdFat_NoArduino/src/SpiDriver/SdSpiLibDriver.h |   2 +-
 .../src/SpiDriver/SdSpiParticle.cpp                |   2 +-
 lib/SdFat_NoArduino/src/SpiDriver/SdSpiSTM32.cpp   |   2 +-
 .../src/SpiDriver/SdSpiSTM32Core.cpp               |   2 +-
 .../src/SpiDriver/SdSpiSoftDriver.h                |   2 +-
 lib/SdFat_NoArduino/src/SpiDriver/SdSpiTeensy3.cpp |   2 +-
 lib/SdFat_NoArduino/src/common/ArduinoFiles.h      |   2 +-
 lib/SdFat_NoArduino/src/common/CompileDateTime.h   |   2 +-
 lib/SdFat_NoArduino/src/common/DebugMacros.h       |   2 +-
 lib/SdFat_NoArduino/src/common/FmtNumber.cpp       |   2 +-
 lib/SdFat_NoArduino/src/common/FmtNumber.h         |   2 +-
 lib/SdFat_NoArduino/src/common/FsApiConstants.h    |   2 +-
 lib/SdFat_NoArduino/src/common/FsBlockDevice.h     |   2 +-
 .../src/common/FsBlockDeviceInterface.h            |   2 +-
 lib/SdFat_NoArduino/src/common/FsCache.cpp         |   2 +-
 lib/SdFat_NoArduino/src/common/FsCache.h           |   2 +-
 lib/SdFat_NoArduino/src/common/FsDateTime.cpp      |   2 +-
 lib/SdFat_NoArduino/src/common/FsDateTime.h        |   2 +-
 lib/SdFat_NoArduino/src/common/FsName.cpp          |   2 +-
 lib/SdFat_NoArduino/src/common/FsName.h            |   2 +-
 lib/SdFat_NoArduino/src/common/FsStructs.cpp       |   2 +-
 lib/SdFat_NoArduino/src/common/FsStructs.h         |  78 +--
 lib/SdFat_NoArduino/src/common/FsUtf.cpp           |   2 +-
 lib/SdFat_NoArduino/src/common/FsUtf.h             |   2 +-
 lib/SdFat_NoArduino/src/common/SysCall.h           |   2 +-
 lib/SdFat_NoArduino/src/common/upcase.cpp          |   2 +-
 lib/SdFat_NoArduino/src/common/upcase.h            |   2 +-
 lib/SdFat_NoArduino/src/iostream/ArduinoStream.h   |   2 +-
 lib/SdFat_NoArduino/src/iostream/StdioStream.cpp   |   2 +-
 lib/SdFat_NoArduino/src/iostream/StdioStream.h     |   2 +-
 .../src/iostream/StreamBaseClass.cpp               |   2 +-
 lib/SdFat_NoArduino/src/iostream/bufstream.h       |   2 +-
 lib/SdFat_NoArduino/src/iostream/fstream.h         |   2 +-
 lib/SdFat_NoArduino/src/iostream/ios.h             |   2 +-
 lib/SdFat_NoArduino/src/iostream/iostream.h        |   2 +-
 lib/SdFat_NoArduino/src/iostream/istream.cpp       |   2 +-
 lib/SdFat_NoArduino/src/iostream/istream.h         |   2 +-
 lib/SdFat_NoArduino/src/iostream/ostream.cpp       |   2 +-
 lib/SdFat_NoArduino/src/iostream/ostream.h         |   2 +-
 lib/SdFat_NoArduino/src/sdios.h                    |   2 +-
 116 files changed, 949 insertions(+), 2304 deletions(-)

commit 511e79f8ea42a26f099ff972d700c17e771bae56
Merge: 0afc2232 4d1022eb
Author: Alex Perez <aperez@rabbithole.ws>
Date:   Mon Dec 5 15:12:26 2022 -0800

    Merge pull request #115 from ZuluSCSI/report-proper-term-on-mini-v1_0

    Report proper termination on ZuluSCSI mini v1.0

commit 4d1022eb5f7eef0487e7dab1f4f9162e2302f707
Merge: 11b1b656 0afc2232
Author: Morio <morio.earth@gmail.com>
Date:   Mon Dec 5 15:05:43 2022 -0800

    Merge branch 'main' of github.com:ZuluSCSI/ZuluSCSI-firmware into report-proper-term-on-mini-v1_0

commit 0afc22325dd7a2678dcb55caf3c813c6b86b744e
Merge: 893109fe 5953f59e
Author: Alex Perez <aperez@rabbithole.ws>
Date:   Mon Dec 5 14:55:52 2022 -0800

    Merge pull request #114 from ZuluSCSI/report-firmware-after-time-reset

    Fix reporting time of firmware version

commit 11b1b656be9c08c807effe5d8d85d40f68aef2cf
Author: Morio <morio.earth@gmail.com>
Date:   Mon Dec 5 14:38:37 2022 -0800

    Report proper termination on ZuluSCSI mini v1.0

    Mask a hardware error on some rev2022b boards that are reporting
    the incorrect termination status.

 lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.cpp |  4 ++++
 lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.h   |  6 +++++-
 platformio.ini                                       | 14 +++++++++++++-
 src/ZuluSCSI_config.h                                |  2 +-
 4 files changed, 23 insertions(+), 3 deletions(-)

commit 5953f59e3b0062ab0f9b4ada34a44525f1d30c98
Author: Morio <morio.earth@gmail.com>
Date:   Mon Dec 5 14:02:19 2022 -0800

    Fix reporting time of firmware version

    Move firmware version logging to platform code after millisecond
    clock is reset.

 lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.cpp | 3 +++
 src/ZuluSCSI.cpp                                     | 6 ------
 2 files changed, 3 insertions(+), 6 deletions(-)

commit 893109fe8cac3214a1f3714c040a893910719905
Author: Alex Perez <aperez@rabbithole.ws>
Date:   Mon Dec 5 10:50:10 2022 -0800

    Bump firmware rev to 1.1.4 in preparation for release

 src/ZuluSCSI_config.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

commit c23c08c4706d765408154caa2acec25c674d4722
Author: Petteri Aimonen <jpa@git.mail.kapsi.fi>
Date:   Mon Dec 5 09:06:46 2022 +0200

    Dont show ROM drive log messages when platform has none

 src/ZuluSCSI_disk.cpp | 9 +++++++++
 1 file changed, 9 insertions(+)
androda 2 yıl önce
ebeveyn
işleme
45e7f55592
100 değiştirilmiş dosya ile 23650 ekleme ve 432 silme
  1. 1 0
      bluescsi.ini
  2. 67 6
      lib/BlueSCSI_platform_RP2040/BlueSCSI_platform.cpp
  3. 10 3
      lib/BlueSCSI_platform_RP2040/BlueSCSI_platform.h
  4. 2 2
      lib/BlueSCSI_platform_RP2040/rp2040_sdio.cpp
  5. 19 39
      lib/BlueSCSI_platform_RP2040/scsiPhy.cpp
  6. 7 0
      lib/BlueSCSI_platform_RP2040/scsiPhy.h
  7. 55 19
      lib/BlueSCSI_platform_RP2040/scsi_accel.pio
  8. 107 17
      lib/BlueSCSI_platform_RP2040/scsi_accel.pio.h
  9. 534 326
      lib/BlueSCSI_platform_RP2040/scsi_accel_rp2040.cpp
  10. 27 7
      lib/BlueSCSI_platform_RP2040/scsi_accel_rp2040.h
  11. 11 13
      lib/BlueSCSI_platform_RP2040/sd_card_sdio.cpp
  12. 8 0
      lib/BlueSCSI_platform_template/BlueSCSI_platform.cpp
  13. 3 0
      lib/BlueSCSI_platform_template/BlueSCSI_platform.h
  14. 51 0
      lib/SdFat_NoArduino/doc/SdErrorCodes.txt
  15. 289 0
      lib/SdFat_NoArduino/doc/mainpage.h
  16. 899 0
      lib/SdFat_NoArduino/examples/AvrAdcLogger/AvrAdcLogger.ino
  17. 269 0
      lib/SdFat_NoArduino/examples/SdInfo/SdInfo.ino
  18. 274 0
      lib/SdFat_NoArduino/examples/bench/bench.ino
  19. 11 0
      lib/SdFat_NoArduino/library.properties
  20. 269 0
      lib/SdFat_NoArduino/src/BufferedPrint.h
  21. 33 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatConfig.h
  22. 621 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatDbg.cpp
  23. 738 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatFile.cpp
  24. 864 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatFile.h
  25. 228 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatFilePrint.cpp
  26. 756 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatFileWrite.cpp
  27. 363 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatFormatter.cpp
  28. 55 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatFormatter.h
  29. 29 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatLib.h
  30. 194 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatName.cpp
  31. 322 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatPartition.cpp
  32. 231 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatPartition.h
  33. 45 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatVolume.cpp
  34. 357 0
      lib/SdFat_NoArduino/src/ExFatLib/ExFatVolume.h
  35. 268 0
      lib/SdFat_NoArduino/src/FatLib/FatDbg.cpp
  36. 1519 0
      lib/SdFat_NoArduino/src/FatLib/FatFile.cpp
  37. 1086 0
      lib/SdFat_NoArduino/src/FatLib/FatFile.h
  38. 595 0
      lib/SdFat_NoArduino/src/FatLib/FatFileLFN.cpp
  39. 109 0
      lib/SdFat_NoArduino/src/FatLib/FatFilePrint.cpp
  40. 315 0
      lib/SdFat_NoArduino/src/FatLib/FatFileSFN.cpp
  41. 280 0
      lib/SdFat_NoArduino/src/FatLib/FatFormatter.cpp
  42. 66 0
      lib/SdFat_NoArduino/src/FatLib/FatFormatter.h
  43. 29 0
      lib/SdFat_NoArduino/src/FatLib/FatLib.h
  44. 356 0
      lib/SdFat_NoArduino/src/FatLib/FatName.cpp
  45. 489 0
      lib/SdFat_NoArduino/src/FatLib/FatPartition.cpp
  46. 295 0
      lib/SdFat_NoArduino/src/FatLib/FatPartition.h
  47. 45 0
      lib/SdFat_NoArduino/src/FatLib/FatVolume.cpp
  48. 359 0
      lib/SdFat_NoArduino/src/FatLib/FatVolume.h
  49. 80 0
      lib/SdFat_NoArduino/src/FreeStack.cpp
  50. 94 0
      lib/SdFat_NoArduino/src/FreeStack.h
  51. 219 0
      lib/SdFat_NoArduino/src/FsLib/FsFile.cpp
  52. 858 0
      lib/SdFat_NoArduino/src/FsLib/FsFile.h
  53. 57 0
      lib/SdFat_NoArduino/src/FsLib/FsFormatter.h
  54. 34 0
      lib/SdFat_NoArduino/src/FsLib/FsLib.h
  55. 29 0
      lib/SdFat_NoArduino/src/FsLib/FsNew.cpp
  56. 46 0
      lib/SdFat_NoArduino/src/FsLib/FsNew.h
  57. 66 0
      lib/SdFat_NoArduino/src/FsLib/FsVolume.cpp
  58. 410 0
      lib/SdFat_NoArduino/src/FsLib/FsVolume.h
  59. 71 0
      lib/SdFat_NoArduino/src/MinimumSerial.cpp
  60. 67 0
      lib/SdFat_NoArduino/src/MinimumSerial.h
  61. 366 0
      lib/SdFat_NoArduino/src/RingBuf.h
  62. 82 0
      lib/SdFat_NoArduino/src/SdCard/SdCard.h
  63. 45 0
      lib/SdFat_NoArduino/src/SdCard/SdCardInfo.cpp
  64. 420 0
      lib/SdFat_NoArduino/src/SdCard/SdCardInfo.h
  65. 129 0
      lib/SdFat_NoArduino/src/SdCard/SdCardInterface.h
  66. 777 0
      lib/SdFat_NoArduino/src/SdCard/SdSpiCard.cpp
  67. 451 0
      lib/SdFat_NoArduino/src/SdCard/SdSpiCard.h
  68. 267 0
      lib/SdFat_NoArduino/src/SdCard/SdioCard.h
  69. 1120 0
      lib/SdFat_NoArduino/src/SdCard/SdioTeensy.cpp
  70. 514 0
      lib/SdFat_NoArduino/src/SdFat.h
  71. 435 0
      lib/SdFat_NoArduino/src/SdFatConfig.h
  72. 96 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiArduinoDriver.h
  73. 79 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiArtemis.cpp
  74. 124 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiAvr.h
  75. 200 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiBareUnoDriver.h
  76. 78 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiBaseClass.h
  77. 48 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiChipSelect.cpp
  78. 155 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiDriver.h
  79. 216 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiDue.cpp
  80. 96 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiESP.cpp
  81. 90 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiLibDriver.h
  82. 78 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiParticle.cpp
  83. 82 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiSTM32.cpp
  84. 75 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiSTM32Core.cpp
  85. 121 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiSoftDriver.h
  86. 90 0
      lib/SdFat_NoArduino/src/SpiDriver/SdSpiTeensy3.cpp
  87. 159 0
      lib/SdFat_NoArduino/src/common/ArduinoFiles.h
  88. 74 0
      lib/SdFat_NoArduino/src/common/CompileDateTime.h
  89. 74 0
      lib/SdFat_NoArduino/src/common/DebugMacros.h
  90. 515 0
      lib/SdFat_NoArduino/src/common/FmtNumber.cpp
  91. 43 0
      lib/SdFat_NoArduino/src/common/FmtNumber.h
  92. 85 0
      lib/SdFat_NoArduino/src/common/FsApiConstants.h
  93. 33 0
      lib/SdFat_NoArduino/src/common/FsBlockDevice.h
  94. 95 0
      lib/SdFat_NoArduino/src/common/FsBlockDeviceInterface.h
  95. 75 0
      lib/SdFat_NoArduino/src/common/FsCache.cpp
  96. 184 0
      lib/SdFat_NoArduino/src/common/FsCache.h
  97. 175 0
      lib/SdFat_NoArduino/src/common/FsDateTime.cpp
  98. 193 0
      lib/SdFat_NoArduino/src/common/FsDateTime.h
  99. 54 0
      lib/SdFat_NoArduino/src/common/FsName.cpp
  100. 66 0
      lib/SdFat_NoArduino/src/common/FsName.h

+ 1 - 0
bluescsi.ini

@@ -5,6 +5,7 @@ SelectionDelay = 255   # Millisecond delay after selection, 255 = automatic, 0 =
 PhyMode = 0   # 0: Best available  1: PIO  2: DMA_TIMER  3: GREENPAK_PIO   4: GREENPAK_DMA
 #Dir = "/"   # Optionally look for image files in subdirectory
 #Dir2 = "/images"  # Multiple directories can be specified Dir1...Dir9
+#DisableStatusLED 1 # 0: Use status LED, 1: Disable status LED
 
 # Settings that can be needed for compatibility with some hosts
 Quirks = 0   # 0: Standard, 1: Apple, 2: OMTI, 4: Xebec, 8: VMS

+ 67 - 6
lib/BlueSCSI_platform_RP2040/BlueSCSI_platform.cpp

@@ -9,6 +9,7 @@
 #include <hardware/spi.h>
 #include <hardware/structs/xip_ctrl.h>
 #include <platform/mbed_error.h>
+#include <multicore.h>
 
 extern "C" {
 
@@ -20,6 +21,7 @@ extern "C" {
 const char *g_bluescsiplatform_name = PLATFORM_NAME;
 static bool g_scsi_initiator = false;
 static uint32_t g_flash_chip_size = 0;
+static bool g_uart_initialized = false;
 
 void mbed_error_hook(const mbed_error_ctx * error_context);
 
@@ -43,6 +45,9 @@ static void gpio_conf(uint gpio, enum gpio_function fn, bool pullup, bool pulldo
 
 void bluescsiplatform_init()
 {
+    // Make sure second core is stopped
+    multicore_reset_core1();
+
     /* First configure the pins that affect external buffer directions.
      * RP2040 defaults to pulldowns, while these pins have external pull-ups.
      */
@@ -64,6 +69,7 @@ void bluescsiplatform_init()
     /* Initialize logging to SWO pin (UART0) */
     gpio_conf(SWO_PIN,        GPIO_FUNC_UART,false,false, true,  false, true);
     uart_init(uart0, 1000000);
+    g_uart_initialized = true;
     mbed_set_error_hook(mbed_error_hook);
 
     //bluelog("DIP switch settings: debug log ", (int)dbglog, ", termination ", (int)termination);
@@ -228,6 +234,13 @@ bool bluescsiplatform_is_initiator_mode_enabled()
     return g_scsi_initiator;
 }
 
+void azplatform_disable_led(void)
+{   
+    //        pin      function       pup   pdown  out    state fast
+    gpio_conf(LED_PIN, GPIO_FUNC_SIO, false,false, false, false, false);
+    bluelog("Disabling status LED");
+}
+
 /*****************************************/
 /* Crash handlers                        */
 /*****************************************/
@@ -306,11 +319,10 @@ void mbed_error_hook(const mbed_error_ctx * error_context)
 // This function is called for every log message.
 void bluescsiplatform_log(const char *s)
 {
-    #ifdef IS_STANDALONE_2040
-    uart_puts(uart0, s);
-    #else
-    Serial.print(s);
-    #endif
+    if (g_uart_initialized)
+    {
+        uart_puts(uart0, s);
+    }
 }
 
 static int g_watchdog_timeout;
@@ -545,6 +557,8 @@ bool azplatform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t cou
 
 /* A lookup table is the fastest way to calculate parity and convert the IO pin mapping for data bus.
  * For RP2040 we expect that the bits are consecutive and in order.
+ * The PIO-based parity scheme also requires that the lookup table is aligned to 512-byte increment.
+ * The parity table is placed into SRAM4 area to reduce bus contention.
  */
 
 #define PARITY(n) ((1 ^ (n) ^ ((n)>>1) ^ ((n)>>2) ^ ((n)>>3) ^ ((n)>>4) ^ ((n)>>5) ^ ((n)>>6) ^ ((n)>>7)) & 1)
@@ -560,7 +574,7 @@ bool azplatform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t cou
     (PARITY(n)  ? 0 : (1 << SCSI_IO_DBP)) \
 )
 
-const uint32_t g_scsi_parity_lookup[256] =
+const uint16_t g_scsi_parity_lookup[256] __attribute__((aligned(512), section(".scratch_x.parity"))) =
 {
     X(0x00), X(0x01), X(0x02), X(0x03), X(0x04), X(0x05), X(0x06), X(0x07), X(0x08), X(0x09), X(0x0a), X(0x0b), X(0x0c), X(0x0d), X(0x0e), X(0x0f),
     X(0x10), X(0x11), X(0x12), X(0x13), X(0x14), X(0x15), X(0x16), X(0x17), X(0x18), X(0x19), X(0x1a), X(0x1b), X(0x1c), X(0x1d), X(0x1e), X(0x1f),
@@ -582,6 +596,53 @@ const uint32_t g_scsi_parity_lookup[256] =
 
 #undef X
 
+/* Similarly, another lookup table is used to verify parity of received data.
+ * This table is indexed by the 8 data bits + 1 parity bit from SCSI bus (active low)
+ * Each word contains the data byte (inverted to active-high) and a bit indicating whether parity is valid.
+ */
+#define X(n) (\
+    ((n & 0xFF) ^ 0xFF) | \
+    (((PARITY(n & 0xFF) ^ (n >> 8)) & 1) << 8) \
+)
+
+const uint16_t g_scsi_parity_check_lookup[512] __attribute__((aligned(1024), section(".scratch_x.parity"))) =
+{
+    X(0x000), X(0x001), X(0x002), X(0x003), X(0x004), X(0x005), X(0x006), X(0x007), X(0x008), X(0x009), X(0x00a), X(0x00b), X(0x00c), X(0x00d), X(0x00e), X(0x00f),
+    X(0x010), X(0x011), X(0x012), X(0x013), X(0x014), X(0x015), X(0x016), X(0x017), X(0x018), X(0x019), X(0x01a), X(0x01b), X(0x01c), X(0x01d), X(0x01e), X(0x01f),
+    X(0x020), X(0x021), X(0x022), X(0x023), X(0x024), X(0x025), X(0x026), X(0x027), X(0x028), X(0x029), X(0x02a), X(0x02b), X(0x02c), X(0x02d), X(0x02e), X(0x02f),
+    X(0x030), X(0x031), X(0x032), X(0x033), X(0x034), X(0x035), X(0x036), X(0x037), X(0x038), X(0x039), X(0x03a), X(0x03b), X(0x03c), X(0x03d), X(0x03e), X(0x03f),
+    X(0x040), X(0x041), X(0x042), X(0x043), X(0x044), X(0x045), X(0x046), X(0x047), X(0x048), X(0x049), X(0x04a), X(0x04b), X(0x04c), X(0x04d), X(0x04e), X(0x04f),
+    X(0x050), X(0x051), X(0x052), X(0x053), X(0x054), X(0x055), X(0x056), X(0x057), X(0x058), X(0x059), X(0x05a), X(0x05b), X(0x05c), X(0x05d), X(0x05e), X(0x05f),
+    X(0x060), X(0x061), X(0x062), X(0x063), X(0x064), X(0x065), X(0x066), X(0x067), X(0x068), X(0x069), X(0x06a), X(0x06b), X(0x06c), X(0x06d), X(0x06e), X(0x06f),
+    X(0x070), X(0x071), X(0x072), X(0x073), X(0x074), X(0x075), X(0x076), X(0x077), X(0x078), X(0x079), X(0x07a), X(0x07b), X(0x07c), X(0x07d), X(0x07e), X(0x07f),
+    X(0x080), X(0x081), X(0x082), X(0x083), X(0x084), X(0x085), X(0x086), X(0x087), X(0x088), X(0x089), X(0x08a), X(0x08b), X(0x08c), X(0x08d), X(0x08e), X(0x08f),
+    X(0x090), X(0x091), X(0x092), X(0x093), X(0x094), X(0x095), X(0x096), X(0x097), X(0x098), X(0x099), X(0x09a), X(0x09b), X(0x09c), X(0x09d), X(0x09e), X(0x09f),
+    X(0x0a0), X(0x0a1), X(0x0a2), X(0x0a3), X(0x0a4), X(0x0a5), X(0x0a6), X(0x0a7), X(0x0a8), X(0x0a9), X(0x0aa), X(0x0ab), X(0x0ac), X(0x0ad), X(0x0ae), X(0x0af),
+    X(0x0b0), X(0x0b1), X(0x0b2), X(0x0b3), X(0x0b4), X(0x0b5), X(0x0b6), X(0x0b7), X(0x0b8), X(0x0b9), X(0x0ba), X(0x0bb), X(0x0bc), X(0x0bd), X(0x0be), X(0x0bf),
+    X(0x0c0), X(0x0c1), X(0x0c2), X(0x0c3), X(0x0c4), X(0x0c5), X(0x0c6), X(0x0c7), X(0x0c8), X(0x0c9), X(0x0ca), X(0x0cb), X(0x0cc), X(0x0cd), X(0x0ce), X(0x0cf),
+    X(0x0d0), X(0x0d1), X(0x0d2), X(0x0d3), X(0x0d4), X(0x0d5), X(0x0d6), X(0x0d7), X(0x0d8), X(0x0d9), X(0x0da), X(0x0db), X(0x0dc), X(0x0dd), X(0x0de), X(0x0df),
+    X(0x0e0), X(0x0e1), X(0x0e2), X(0x0e3), X(0x0e4), X(0x0e5), X(0x0e6), X(0x0e7), X(0x0e8), X(0x0e9), X(0x0ea), X(0x0eb), X(0x0ec), X(0x0ed), X(0x0ee), X(0x0ef),
+    X(0x0f0), X(0x0f1), X(0x0f2), X(0x0f3), X(0x0f4), X(0x0f5), X(0x0f6), X(0x0f7), X(0x0f8), X(0x0f9), X(0x0fa), X(0x0fb), X(0x0fc), X(0x0fd), X(0x0fe), X(0x0ff),
+    X(0x100), X(0x101), X(0x102), X(0x103), X(0x104), X(0x105), X(0x106), X(0x107), X(0x108), X(0x109), X(0x10a), X(0x10b), X(0x10c), X(0x10d), X(0x10e), X(0x10f),
+    X(0x110), X(0x111), X(0x112), X(0x113), X(0x114), X(0x115), X(0x116), X(0x117), X(0x118), X(0x119), X(0x11a), X(0x11b), X(0x11c), X(0x11d), X(0x11e), X(0x11f),
+    X(0x120), X(0x121), X(0x122), X(0x123), X(0x124), X(0x125), X(0x126), X(0x127), X(0x128), X(0x129), X(0x12a), X(0x12b), X(0x12c), X(0x12d), X(0x12e), X(0x12f),
+    X(0x130), X(0x131), X(0x132), X(0x133), X(0x134), X(0x135), X(0x136), X(0x137), X(0x138), X(0x139), X(0x13a), X(0x13b), X(0x13c), X(0x13d), X(0x13e), X(0x13f),
+    X(0x140), X(0x141), X(0x142), X(0x143), X(0x144), X(0x145), X(0x146), X(0x147), X(0x148), X(0x149), X(0x14a), X(0x14b), X(0x14c), X(0x14d), X(0x14e), X(0x14f),
+    X(0x150), X(0x151), X(0x152), X(0x153), X(0x154), X(0x155), X(0x156), X(0x157), X(0x158), X(0x159), X(0x15a), X(0x15b), X(0x15c), X(0x15d), X(0x15e), X(0x15f),
+    X(0x160), X(0x161), X(0x162), X(0x163), X(0x164), X(0x165), X(0x166), X(0x167), X(0x168), X(0x169), X(0x16a), X(0x16b), X(0x16c), X(0x16d), X(0x16e), X(0x16f),
+    X(0x170), X(0x171), X(0x172), X(0x173), X(0x174), X(0x175), X(0x176), X(0x177), X(0x178), X(0x179), X(0x17a), X(0x17b), X(0x17c), X(0x17d), X(0x17e), X(0x17f),
+    X(0x180), X(0x181), X(0x182), X(0x183), X(0x184), X(0x185), X(0x186), X(0x187), X(0x188), X(0x189), X(0x18a), X(0x18b), X(0x18c), X(0x18d), X(0x18e), X(0x18f),
+    X(0x190), X(0x191), X(0x192), X(0x193), X(0x194), X(0x195), X(0x196), X(0x197), X(0x198), X(0x199), X(0x19a), X(0x19b), X(0x19c), X(0x19d), X(0x19e), X(0x19f),
+    X(0x1a0), X(0x1a1), X(0x1a2), X(0x1a3), X(0x1a4), X(0x1a5), X(0x1a6), X(0x1a7), X(0x1a8), X(0x1a9), X(0x1aa), X(0x1ab), X(0x1ac), X(0x1ad), X(0x1ae), X(0x1af),
+    X(0x1b0), X(0x1b1), X(0x1b2), X(0x1b3), X(0x1b4), X(0x1b5), X(0x1b6), X(0x1b7), X(0x1b8), X(0x1b9), X(0x1ba), X(0x1bb), X(0x1bc), X(0x1bd), X(0x1be), X(0x1bf),
+    X(0x1c0), X(0x1c1), X(0x1c2), X(0x1c3), X(0x1c4), X(0x1c5), X(0x1c6), X(0x1c7), X(0x1c8), X(0x1c9), X(0x1ca), X(0x1cb), X(0x1cc), X(0x1cd), X(0x1ce), X(0x1cf),
+    X(0x1d0), X(0x1d1), X(0x1d2), X(0x1d3), X(0x1d4), X(0x1d5), X(0x1d6), X(0x1d7), X(0x1d8), X(0x1d9), X(0x1da), X(0x1db), X(0x1dc), X(0x1dd), X(0x1de), X(0x1df),
+    X(0x1e0), X(0x1e1), X(0x1e2), X(0x1e3), X(0x1e4), X(0x1e5), X(0x1e6), X(0x1e7), X(0x1e8), X(0x1e9), X(0x1ea), X(0x1eb), X(0x1ec), X(0x1ed), X(0x1ee), X(0x1ef),
+    X(0x1f0), X(0x1f1), X(0x1f2), X(0x1f3), X(0x1f4), X(0x1f5), X(0x1f6), X(0x1f7), X(0x1f8), X(0x1f9), X(0x1fa), X(0x1fb), X(0x1fc), X(0x1fd), X(0x1fe), X(0x1ff),
+};
+
+#undef X
+
 } /* extern "C" */
 
 /* Logging from mbed */

+ 10 - 3
lib/BlueSCSI_platform_RP2040/BlueSCSI_platform.h

@@ -16,8 +16,8 @@ extern const char *g_bluescsiplatform_name;
 #define PLATFORM_NAME "BlueSCSI RP2040"
 #define PLATFORM_REVISION "2.0"
 #define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_10
-#define PLATFORM_OPTIMAL_MIN_SD_WRITE_SIZE 4096
-#define PLATFORM_OPTIMAL_MAX_SD_WRITE_SIZE 32768
+#define PLATFORM_OPTIMAL_MIN_SD_WRITE_SIZE 32768
+#define PLATFORM_OPTIMAL_MAX_SD_WRITE_SIZE 65536
 #define PLATFORM_OPTIMAL_LAST_SD_WRITE_SIZE 8192
 #define SD_USE_SDIO 1
 #define PLATFORM_HAS_INITIATOR_MODE 1
@@ -54,6 +54,9 @@ void bluescsiplatform_init();
 // Initialization for main application, not used for bootloader
 void bluescsiplatform_late_init();
 
+// Disable the status LED
+void azplatform_disable_led(void);
+
 // Query whether initiator mode is enabled on targets with PLATFORM_HAS_INITIATOR_MODE
 bool bluescsiplatform_is_initiator_mode_enabled();
 
@@ -88,6 +91,11 @@ bool azplatform_read_romdrive(uint8_t *dest, uint32_t start, uint32_t count);
 bool azplatform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count);
 #endif
 
+// Parity lookup tables for write and read from SCSI bus.
+// These are used by macros below and the code in scsi_accel_rp2040.cpp
+extern const uint16_t g_scsi_parity_lookup[256];
+extern const uint16_t g_scsi_parity_check_lookup[512];
+
 // Below are GPIO access definitions that are used from scsiPhy.cpp.
 
 // Write a single SCSI pin.
@@ -120,7 +128,6 @@ bool azplatform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t cou
      sio_hw->gpio_oe_set = SCSI_IO_DATA_MASK)
 
 // Write SCSI data bus, also sets REQ to inactive.
-extern const uint32_t g_scsi_parity_lookup[256];
 #define SCSI_OUT_DATA(data) \
     gpio_put_masked(SCSI_IO_DATA_MASK | (1 << SCSI_OUT_REQ), \
                     g_scsi_parity_lookup[(uint8_t)(data)] | (1 << SCSI_OUT_REQ)), \

+ 2 - 2
lib/BlueSCSI_platform_RP2040/rp2040_sdio.cpp

@@ -19,8 +19,8 @@
 #define SDIO_PIO pio1
 #define SDIO_CMD_SM 0
 #define SDIO_DATA_SM 1
-#define SDIO_DMA_CH 2
-#define SDIO_DMA_CHB 3
+#define SDIO_DMA_CH 4
+#define SDIO_DMA_CHB 5
 
 // Maximum number of 512 byte blocks to transfer in one request
 #define SDIO_MAX_BLOCKS 256

+ 19 - 39
lib/BlueSCSI_platform_RP2040/scsiPhy.cpp

@@ -191,13 +191,13 @@ extern "C" uint32_t scsiEnterPhaseImmediate(int phase)
         scsiLogPhaseChange(phase);
 
         // Select between synchronous vs. asynchronous SCSI writes
-        if (g_scsi_phase == DATA_IN && scsiDev.target->syncOffset > 0)
+        if (scsiDev.target->syncOffset > 0 && (g_scsi_phase == DATA_IN || g_scsi_phase == DATA_OUT))
         {
-            scsi_accel_rp2040_setWriteMode(scsiDev.target->syncOffset, scsiDev.target->syncPeriod);
+            scsi_accel_rp2040_setSyncMode(scsiDev.target->syncOffset, scsiDev.target->syncPeriod);
         }
         else
         {
-            scsi_accel_rp2040_setWriteMode(0, 0);
+            scsi_accel_rp2040_setSyncMode(0, 0);
         }
 
         if (phase < 0)
@@ -300,22 +300,7 @@ extern "C" void scsiWrite(const uint8_t* data, uint32_t count)
 extern "C" void scsiStartWrite(const uint8_t* data, uint32_t count)
 {
     scsiLogDataIn(data, count);
-
-    if ((count & 1) != 0 || ((uint32_t)data & 1) != 0)
-    {
-        // Unaligned write, do it byte-by-byte
-        scsiFinishWrite();
-        for (uint32_t i = 0; i < count; i++)
-        {
-            if (scsiDev.resetFlag) break;
-            scsiWriteOneByte(data[i]);
-        }
-    }
-    else
-    {
-        // Use accelerated routine
-        scsi_accel_rp2040_startWrite(data, count, &scsiDev.resetFlag);
-    }
+    scsi_accel_rp2040_startWrite(data, count, &scsiDev.resetFlag);
 }
 
 extern "C" bool scsiIsWriteFinished(const uint8_t *data)
@@ -361,27 +346,22 @@ extern "C" uint8_t scsiReadByte(void)
 extern "C" void scsiRead(uint8_t* data, uint32_t count, int* parityError)
 {
     *parityError = 0;
+    scsiStartRead(data, count, parityError);
+    scsiFinishRead(data, count, parityError);
+}
 
-    if ((count & 1) != 0 || ((uint32_t)data & 1) != 0)
-    {
-        // Unaligned transfer, do byte by byte
-        for (uint32_t i = 0; i < count; i++)
-        {
-            if (scsiDev.resetFlag) break;
-            data[i] = scsiReadOneByte(parityError);
-        }
-    }
-    else
-    {
-        // Use accelerated routine
-        scsi_accel_rp2040_read(data, count, parityError, &scsiDev.resetFlag);
-
-    }
-
-    if(*parityError && (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
-    {
-        bluelog("Parity error in scsiRead()");
-    }
+extern "C" void scsiStartRead(uint8_t* data, uint32_t count, int *parityError)
+{
+    scsi_accel_rp2040_startRead(data, count, parityError, &scsiDev.resetFlag);
+}
 
+extern "C" void scsiFinishRead(uint8_t* data, uint32_t count, int *parityError)
+{
+    scsi_accel_rp2040_finishRead(data, count, parityError, &scsiDev.resetFlag);
     scsiLogDataOut(data, count);
 }
+
+extern "C" bool scsiIsReadFinished(const uint8_t *data)
+{
+    return scsi_accel_rp2040_isReadFinished(data);
+}

+ 7 - 0
lib/BlueSCSI_platform_RP2040/scsiPhy.h

@@ -54,11 +54,18 @@ uint8_t scsiReadByte(void);
 // either combine transfers or block until previous transfer completes.
 void scsiStartWrite(const uint8_t* data, uint32_t count);
 void scsiFinishWrite();
+void scsiStartRead(uint8_t* data, uint32_t count, int *parityError);
+void scsiFinishRead(uint8_t* data, uint32_t count, int *parityError);
 
 // Query whether the data at pointer has already been read, i.e. buffer can be reused.
 // If data is NULL, checks if all writes have completed.
 bool scsiIsWriteFinished(const uint8_t *data);
 
+// Query whether the data at pointer has already been written, i.e. can be processed.
+// If data is NULL, checks if all reads have completed.
+bool scsiIsReadFinished(const uint8_t *data);
+
+#define PLATFORM_SCSIPHY_HAS_NONBLOCKING_READ 1
 
 #define s2s_getScsiRateKBs() 0
 

+ 55 - 19
lib/BlueSCSI_platform_RP2040/scsi_accel.pio

@@ -13,39 +13,48 @@
 ; One clock cycle is 8 ns => delay 7 clocks
 .define REQ_DLY 7
 
+; Adds parity to data that is to be written to SCSI
+; This works by generating addresses for DMA to fetch data from.
+; Register X should be initialized to the base address of the lookup table.
+.program scsi_parity
+    pull block
+    in NULL, 1
+    in OSR, 8
+    in X, 23
+
 ; Write to SCSI bus using asynchronous handshake.
-; Data is written as 16-bit words that contain the 8 data bits + 1 parity bit.
-; 7 bits in each word are discarded.
+; Data is written as 32-bit words that contain the 8 data bits + 1 parity bit.
+; 23 bits in each word are discarded.
 ; Number of bytes to send must be multiple of 2.
 .program scsi_accel_async_write
     .side_set 1
 
     pull ifempty block          side 1  ; Get data from TX FIFO
     out pins, 9                 side 1  ; Write data and parity bit
-    out null, 7 [REQ_DLY-2]     side 1  ; Discard unused bits, wait for data preset time
+    out null, 23 [REQ_DLY-2]    side 1  ; Discard unused bits, wait for data preset time
     wait 1 gpio ACK             side 1  ; Wait for ACK to be inactive
     wait 0 gpio ACK             side 0  ; Assert REQ, wait for ACK low
 
-; Read from SCSI bus using asynchronous handshake.
-; Also works for synchronous mode down to 50 ns transfer period.
-; Data is returned as 16-bit words that contain the 8 data bits + 1 parity bit.
-; Number of bytes to receive minus 1 should be written to TX fifo.
-; Number of bytes to receive must be divisible by 2.
-.program scsi_accel_async_read
+; Read from SCSI bus using sync or async handshake.
+; Data is returned as 32-bit words:
+; - bit  0: always zero
+; - bits 1-8: data byte
+; - bit  9: parity bit
+; - bits 10-31: lookup table address
+; Lookup table address should be loaded into register Y.
+; One dummy word should be written to TX fifo for every byte to receive.
+.program scsi_accel_read
     .side_set 1
 
-    pull block                  side 1  ; Get number of bytes to receive
-    mov x, osr                  side 1  ; Store to counter X
-
-start:
+    pull block                  side 1  ; Pull from TX fifo for counting bytes and pacing sync mode
     wait 1 gpio ACK             side 1  ; Wait for ACK high
+    in null, 1                  side 0  ; Zero bit because lookup table entries are 16-bit
     wait 0 gpio ACK             side 0  ; Assert REQ, wait for ACK low
     in pins, 9                  side 1  ; Deassert REQ, read GPIO
-    in null, 7                  side 1  ; Padding bits
-    jmp x-- start               side 1  ; Decrement byte count and jump to start
+    in y, 22                    side 1  ; Copy parity lookup table address
 
 ; Data state machine for synchronous writes.
-; Takes the lowest 9 bits of each 16 bit word and writes them to bus with REQ pulse.
+; Takes the lowest 9 bits of each 32 bit word and writes them to bus with REQ pulse.
 ; The delay times will be rewritten by C code to match the negotiated SCSI sync speed.
 ;
 ; Shifts one bit to ISR per every byte transmitted. This is used to control the transfer
@@ -54,9 +63,9 @@ start:
 .program scsi_sync_write
     .side_set 1
 
-    out pins, 9     [0]         side 1  ; Write data and parity bit, wait for deskew delay
-    out null, 7     [0]         side 0  ; Assert REQ, wait for assert time
-    in null, 1      [0]         side 1  ; Deassert REQ, wait for transfer period, wait for space in ACK buffer
+    out pins, 9      [0]        side 1  ; Write data and parity bit, wait for deskew delay
+    out null, 23     [0]        side 0  ; Assert REQ, wait for assert time
+    in null, 1       [0]        side 1  ; Deassert REQ, wait for transfer period, wait for space in ACK buffer
 
 ; Data pacing state machine for synchronous writes.
 ; Takes one bit from ISR on every falling edge of ACK.
@@ -66,3 +75,30 @@ start:
     wait 1 gpio ACK
     wait 0 gpio ACK   ; Wait for falling edge on ACK
     out null, 1       ; Let scsi_sync_write send one more byte
+
+; Data pacing state machine for synchronous reads.
+; The delay times will be rewritten by C code to match the negotiated SCSI sync speed.
+; Number of bytes to receive minus one should be loaded into register X.
+; In synchronous mode this generates the REQ pulses and dummy words.
+; In asynchronous mode it just generates dummy words to feed to scsi_accel_read.
+.program scsi_sync_read_pacer
+    .side_set 1
+
+start:
+    push block      [0]      side 1  ; Send dummy word to scsi_accel_read, wait for transfer period
+    jmp x-- start   [0]      side 0  ; Assert REQ, wait for assert time
+
+finish:
+    jmp finish      [0]      side 1
+
+; Parity checker for reads from SCSI bus.
+; Receives 16-bit words from g_scsi_parity_check_lookup
+; Bottom 8 bits are the data byte, which is passed to output FIFO
+; The 9th bit is parity valid bit, which is 1 for valid and 0 for parity error.
+.program scsi_read_parity
+parity_valid:
+    out isr, 8                ; Take the 8 data bits for passing to RX fifo
+    push block                ; Push the data to RX fifo
+    out x, 24                 ; Take the parity valid bit, and the rest of 32-bit word
+    jmp x-- parity_valid      ; If parity valid bit is 1, repeat from start
+    irq set 0                 ; Parity error, set interrupt flag

+ 107 - 17
lib/BlueSCSI_platform_RP2040/scsi_accel.pio.h

@@ -8,6 +8,36 @@
 #include "hardware/pio.h"
 #endif
 
+// ----------- //
+// scsi_parity //
+// ----------- //
+
+#define scsi_parity_wrap_target 0
+#define scsi_parity_wrap 3
+
+static const uint16_t scsi_parity_program_instructions[] = {
+            //     .wrap_target
+    0x80a0, //  0: pull   block                      
+    0x4061, //  1: in     null, 1                    
+    0x40e8, //  2: in     osr, 8                     
+    0x4037, //  3: in     x, 23                      
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program scsi_parity_program = {
+    .instructions = scsi_parity_program_instructions,
+    .length = 4,
+    .origin = -1,
+};
+
+static inline pio_sm_config scsi_parity_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + scsi_parity_wrap_target, offset + scsi_parity_wrap);
+    return c;
+}
+#endif
+
 // ---------------------- //
 // scsi_accel_async_write //
 // ---------------------- //
@@ -19,7 +49,7 @@ static const uint16_t scsi_accel_async_write_program_instructions[] = {
             //     .wrap_target
     0x90e0, //  0: pull   ifempty block   side 1     
     0x7009, //  1: out    pins, 9         side 1     
-    0x7567, //  2: out    null, 7         side 1 [5] 
+    0x7577, //  2: out    null, 23        side 1 [5] 
     0x309a, //  3: wait   1 gpio, 26      side 1     
     0x201a, //  4: wait   0 gpio, 26      side 0     
             //     .wrap
@@ -40,35 +70,34 @@ static inline pio_sm_config scsi_accel_async_write_program_get_default_config(ui
 }
 #endif
 
-// --------------------- //
-// scsi_accel_async_read //
-// --------------------- //
+// --------------- //
+// scsi_accel_read //
+// --------------- //
 
-#define scsi_accel_async_read_wrap_target 0
-#define scsi_accel_async_read_wrap 6
+#define scsi_accel_read_wrap_target 0
+#define scsi_accel_read_wrap 5
 
-static const uint16_t scsi_accel_async_read_program_instructions[] = {
+static const uint16_t scsi_accel_read_program_instructions[] = {
             //     .wrap_target
     0x90a0, //  0: pull   block           side 1     
-    0xb027, //  1: mov    x, osr          side 1     
-    0x309a, //  2: wait   1 gpio, 26      side 1     
+    0x309a, //  1: wait   1 gpio, 26      side 1     
+    0x4061, //  2: in     null, 1         side 0     
     0x201a, //  3: wait   0 gpio, 26      side 0     
     0x5009, //  4: in     pins, 9         side 1     
-    0x5067, //  5: in     null, 7         side 1     
-    0x1042, //  6: jmp    x--, 2          side 1     
+    0x5056, //  5: in     y, 22           side 1     
             //     .wrap
 };
 
 #if !PICO_NO_HARDWARE
-static const struct pio_program scsi_accel_async_read_program = {
-    .instructions = scsi_accel_async_read_program_instructions,
-    .length = 7,
+static const struct pio_program scsi_accel_read_program = {
+    .instructions = scsi_accel_read_program_instructions,
+    .length = 6,
     .origin = -1,
 };
 
-static inline pio_sm_config scsi_accel_async_read_program_get_default_config(uint offset) {
+static inline pio_sm_config scsi_accel_read_program_get_default_config(uint offset) {
     pio_sm_config c = pio_get_default_sm_config();
-    sm_config_set_wrap(&c, offset + scsi_accel_async_read_wrap_target, offset + scsi_accel_async_read_wrap);
+    sm_config_set_wrap(&c, offset + scsi_accel_read_wrap_target, offset + scsi_accel_read_wrap);
     sm_config_set_sideset(&c, 1, false, false);
     return c;
 }
@@ -84,7 +113,7 @@ static inline pio_sm_config scsi_accel_async_read_program_get_default_config(uin
 static const uint16_t scsi_sync_write_program_instructions[] = {
             //     .wrap_target
     0x7009, //  0: out    pins, 9         side 1     
-    0x6067, //  1: out    null, 7         side 0     
+    0x6077, //  1: out    null, 23        side 0     
     0x5061, //  2: in     null, 1         side 1     
             //     .wrap
 };
@@ -132,3 +161,64 @@ static inline pio_sm_config scsi_sync_write_pacer_program_get_default_config(uin
     return c;
 }
 #endif
+
+// -------------------- //
+// scsi_sync_read_pacer //
+// -------------------- //
+
+#define scsi_sync_read_pacer_wrap_target 0
+#define scsi_sync_read_pacer_wrap 2
+
+static const uint16_t scsi_sync_read_pacer_program_instructions[] = {
+            //     .wrap_target
+    0x9020, //  0: push   block           side 1     
+    0x0040, //  1: jmp    x--, 0          side 0     
+    0x1002, //  2: jmp    2               side 1     
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program scsi_sync_read_pacer_program = {
+    .instructions = scsi_sync_read_pacer_program_instructions,
+    .length = 3,
+    .origin = -1,
+};
+
+static inline pio_sm_config scsi_sync_read_pacer_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + scsi_sync_read_pacer_wrap_target, offset + scsi_sync_read_pacer_wrap);
+    sm_config_set_sideset(&c, 1, false, false);
+    return c;
+}
+#endif
+
+// ---------------- //
+// scsi_read_parity //
+// ---------------- //
+
+#define scsi_read_parity_wrap_target 0
+#define scsi_read_parity_wrap 4
+
+static const uint16_t scsi_read_parity_program_instructions[] = {
+            //     .wrap_target
+    0x60c8, //  0: out    isr, 8                     
+    0x8020, //  1: push   block                      
+    0x6038, //  2: out    x, 24                      
+    0x0040, //  3: jmp    x--, 0                     
+    0xc000, //  4: irq    nowait 0                   
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program scsi_read_parity_program = {
+    .instructions = scsi_read_parity_program_instructions,
+    .length = 5,
+    .origin = -1,
+};
+
+static inline pio_sm_config scsi_read_parity_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + scsi_read_parity_wrap_target, offset + scsi_read_parity_wrap);
+    return c;
+}
+#endif

Dosya farkı çok büyük olduğundan ihmal edildi
+ 534 - 326
lib/BlueSCSI_platform_RP2040/scsi_accel_rp2040.cpp


+ 27 - 7
lib/BlueSCSI_platform_RP2040/scsi_accel_rp2040.h

@@ -6,19 +6,39 @@
 
 void scsi_accel_rp2040_init();
 
-// Set SCSI access mode for write requests.
+// Set SCSI access mode for synchronous transfers
 // Setting syncOffset = 0 enables asynchronous SCSI.
 // Setting syncOffset > 0 enables synchronous SCSI.
-void scsi_accel_rp2040_setWriteMode(int syncOffset, int syncPeriod);
+void scsi_accel_rp2040_setSyncMode(int syncOffset, int syncPeriod);
 
+// Queue a request to write data from the buffer to SCSI bus.
+// This function typically returns immediately and the request will complete in background.
+// If there are too many queued requests, this function will block until previous request finishes.
 void scsi_accel_rp2040_startWrite(const uint8_t* data, uint32_t count, volatile int *resetFlag);
-void scsi_accel_rp2040_stopWrite(volatile int *resetFlag);
-void scsi_accel_rp2040_finishWrite(volatile int *resetFlag);
 
 // Query whether the data at pointer has already been read, i.e. buffer can be reused.
 // If data is NULL, checks if all writes have completed.
 bool scsi_accel_rp2040_isWriteFinished(const uint8_t* data);
 
-// Read data from SCSI bus.
-// Works for both asynchronous and synchronous modes.
-void scsi_accel_rp2040_read(uint8_t *buf, uint32_t count, int *parityError, volatile int *resetFlag);
+// Wait for all write requests to finish and release the bus.
+// If resetFlag is non-zero, aborts write immediately.
+void scsi_accel_rp2040_finishWrite(volatile int *resetFlag);
+
+// Queue a request to read data from SCSI bus to the buffer.
+// This function typically returns immediately and the request will complete in background.
+// If there are too many queued requests, this function will block until previous request finishes.
+void scsi_accel_rp2040_startRead(uint8_t *data, uint32_t count, int *parityError, volatile int *resetFlag);
+
+// Query whether data at address is part of a queued read request.
+// Returns true if there is no outstanding request.
+// If data is NULL, checks if all reads have completed.
+bool scsi_accel_rp2040_isReadFinished(const uint8_t* data);
+
+// Wait for a read request to complete.
+// If buf is not NULL, waits only until the data at data[0] .. data[count-1] is valid.
+// If buf is NULL, waits for all read requests to complete.
+// If there are no further read requests, releases the bus.
+// If resetFlag is non-zero, aborts read immediately.
+// If a parity error has been noticed in any buffer since starting the read, parityError is set to 1.
+void scsi_accel_rp2040_finishRead(const uint8_t *data, uint32_t count, int *parityError, volatile int *resetFlag);
+

+ 11 - 13
lib/BlueSCSI_platform_RP2040/sd_card_sdio.cpp

@@ -189,18 +189,6 @@ bool SdioCard::readCSD(csd_t* csd)
     return true;
 }
 
-bool SdioCard::readSCR(scr_t *scr)
-{
-    bluelog("SdioCard::readSCR() called but not implemented!");
-    return false;
-}
-
-bool SdioCard::cardCMD6(uint32_t arg, uint8_t* status)
-{
-    bluelog("SdioCard::cardCMD6() called but not implemented!");
-    return false;
-}
-
 bool SdioCard::readOCR(uint32_t* ocr)
 {
     // SDIO mode does not have CMD58, but main program uses this to
@@ -307,8 +295,18 @@ bool SdioCard::writeStop()
 
 bool SdioCard::erase(uint32_t firstSector, uint32_t lastSector)
 {
+    bluelog("SdioCard::erase() not implemented");
+    return false;
+}
+
+bool SdioCard::cardCMD6(uint32_t arg, uint8_t* status) {
+    bluelog("SdioCard::cardCMD6() not implemented");
+    return false;
+}
+
+bool SdioCard::readSCR(scr_t* scr) {
+    bluelog("SdioCard::readSCR() not implemented");
     return false;
-    // return checkReturnOk(sd_erase(firstSector * 512, lastSector * 512));
 }
 
 /* Writing and reading, with progress callback */

+ 8 - 0
lib/BlueSCSI_platform_template/BlueSCSI_platform.cpp

@@ -27,6 +27,14 @@ void bluescsiplatform_late_init()
      */
 }
 
+void azplatform_disable_led(void)
+{
+    /* This function disables the LED on the ZuluSCSI board
+    *  Generally by switching the pin from output to input.
+    */
+}
+
+
 /*****************************************/
 /* Debug logging and watchdor            */
 /*****************************************/

+ 3 - 0
lib/BlueSCSI_platform_template/BlueSCSI_platform.h

@@ -46,6 +46,9 @@ void bluescsiplatform_init();
 // Initialization for main application, not used for bootloader
 void bluescsiplatform_late_init();
 
+// Disable the status LED
+void azplatform_disable_led(void);
+
 // Setup soft watchdog if supported
 void bluescsiplatform_reset_watchdog();
 

+ 51 - 0
lib/SdFat_NoArduino/doc/SdErrorCodes.txt

@@ -0,0 +1,51 @@
+2022-07-01
+
+Run the SdErrorCode example to produce an updated list.
+
+Code,Symbol - failed operation
+0X00,SD_CARD_ERROR_NONE - No error
+0X01,SD_CARD_ERROR_CMD0 - Card reset failed
+0X02,SD_CARD_ERROR_CMD2 - SDIO read CID
+0X03,SD_CARD_ERROR_CMD3 - SDIO publish RCA
+0X04,SD_CARD_ERROR_CMD6 - Switch card function
+0X05,SD_CARD_ERROR_CMD7 - SDIO card select
+0X06,SD_CARD_ERROR_CMD8 - Send and check interface settings
+0X07,SD_CARD_ERROR_CMD9 - Read CSD data
+0X08,SD_CARD_ERROR_CMD10 - Read CID data
+0X09,SD_CARD_ERROR_CMD12 - Stop multiple block read
+0X0A,SD_CARD_ERROR_CMD13 - Read card status
+0X0B,SD_CARD_ERROR_CMD17 - Read single block
+0X0C,SD_CARD_ERROR_CMD18 - Read multiple blocks
+0X0D,SD_CARD_ERROR_CMD24 - Write single block
+0X0E,SD_CARD_ERROR_CMD25 - Write multiple blocks
+0X0F,SD_CARD_ERROR_CMD32 - Set first erase block
+0X10,SD_CARD_ERROR_CMD33 - Set last erase block
+0X11,SD_CARD_ERROR_CMD38 - Erase selected blocks
+0X12,SD_CARD_ERROR_CMD58 - Read OCR register
+0X13,SD_CARD_ERROR_CMD59 - Set CRC mode
+0X14,SD_CARD_ERROR_ACMD6 - Set SDIO bus width
+0X15,SD_CARD_ERROR_ACMD13 - Read extended status
+0X16,SD_CARD_ERROR_ACMD23 - Set pre-erased count
+0X17,SD_CARD_ERROR_ACMD41 - Activate card initialization
+0X18,SD_CARD_ERROR_ACMD51 - Read SCR data
+0X19,SD_CARD_ERROR_READ_TOKEN - Bad read data token
+0X1A,SD_CARD_ERROR_READ_CRC - Read CRC error
+0X1B,SD_CARD_ERROR_READ_FIFO - SDIO fifo read timeout
+0X1C,SD_CARD_ERROR_READ_REG - Read CID or CSD failed.
+0X1D,SD_CARD_ERROR_READ_START - Bad readStart argument
+0X1E,SD_CARD_ERROR_READ_TIMEOUT - Read data timeout
+0X1F,SD_CARD_ERROR_STOP_TRAN - Multiple block stop failed
+0X20,SD_CARD_ERROR_TRANSFER_COMPLETE - SDIO transfer complete
+0X21,SD_CARD_ERROR_WRITE_DATA - Write data not accepted
+0X22,SD_CARD_ERROR_WRITE_FIFO - SDIO fifo write timeout
+0X23,SD_CARD_ERROR_WRITE_START - Bad writeStart argument
+0X24,SD_CARD_ERROR_WRITE_PROGRAMMING - Flash programming
+0X25,SD_CARD_ERROR_WRITE_TIMEOUT - Write timeout
+0X26,SD_CARD_ERROR_DMA - DMA transfer failed
+0X27,SD_CARD_ERROR_ERASE - Card did not accept erase commands
+0X28,SD_CARD_ERROR_ERASE_SINGLE_SECTOR - Card does not support erase
+0X29,SD_CARD_ERROR_ERASE_TIMEOUT - Erase command timeout
+0X2A,SD_CARD_ERROR_INIT_NOT_CALLED - Card has not been initialized
+0X2B,SD_CARD_ERROR_INVALID_CARD_CONFIG - Invalid card config
+0X2C,SD_CARD_ERROR_FUNCTION_NOT_SUPPORTED - Unsupported SDIO command
+0X2D,SD_CARD_ERROR_UNKNOWN - Unknown error

+ 289 - 0
lib/SdFat_NoArduino/doc/mainpage.h

@@ -0,0 +1,289 @@
+/**
+ * Copyright (c) 2011-2021 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+/**
+\mainpage Arduino %SdFat Library
+
+\section Warn Warnings for SdFat V2
+
+This is a major new version of SdFat. It is mostly
+backward compatible with SdFat Version 1 for FAT16/FAT32 cards.
+
+You should edit SdFatConfig.h to select features. The default version of
+SdFatConfig.h is suitable for UNO and other small AVR boards.
+
+\section Intro Introduction
+
+The Arduino %SdFat library supports FAT16, FAT32, and exFAT file systems
+on Standard SD, SDHC, and SDXC cards.
+
+In %SdFat version 1, SdFat and File are the main classes.
+
+In %SdFat version 2, SdFat and File are defined by typedefs in terms of the
+following classes.
+
+The file system classes in the %SdFat library are SdFat32, SdExFat, and SdFs.
+SdFat32 supports FAT16 and FAT32. SdExFat supports exFAT, SdFs supports
+FAT16, FAT32, and exFAT.
+
+The corresponding file classes are File32, ExFile, and FsFile.
+
+The types for SdFat and File are defined in SdFatConfig.h. This version
+uses FAT16/FAT32 for small AVR boards and FAT16/FAT32/exFAT for all other
+boards.
+
+\code{.cpp}
+// File types for SdFat, File, SdFile, SdBaseFile, fstream,
+// ifstream, and ofstream.
+//
+// Set SDFAT_FILE_TYPE to:
+// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
+//
+#if defined(__AVR__) && FLASHEND < 0X8000
+// FAT16/FAT32 for 32K AVR boards.
+#define SDFAT_FILE_TYPE 1
+#else  // defined(__AVR__) && FLASHEND < 0X8000
+// FAT16/FAT32 and exFAT for all other boards.
+#define SDFAT_FILE_TYPE 3
+#endif  // defined(__AVR__) && FLASHEND < 0X8000
+\endcode
+
+It is possible to use option three, support or FAT16/FAT32 and exFat
+on an Uno or other AVR board with 32KB flash and 2KB SRAM but memory
+will be very limited.
+
+Uno memory use for a simple data logger is:
+
+> option 1, FAT16/FAT32, 11902 bytes of flash and 855 bytes of SRAM.
+>
+> option 2, exFAT, 14942 bytes of flash and 895 bytes of SRAM.
+>
+> option 3, FAT16/FAT32 and exFAT, 21834 bytes of flash and 908 bytes of SRAM.
+
+Please read documentation under the above classes tab for more information.
+
+A number of example are provided in the %SdFat/examples folder.  These were
+developed to test %SdFat and illustrate its use.
+
+\section exFAT exFAT Features
+
+exFAT has many features not available in FAT16/FAT32.
+
+Files larger than 4GiB, 64-bit file size and file position.
+
+Free space allocation performance improved by using a free space bitmap.
+
+Removal of the physical "." and ".." directory entries that appear in
+FAT16/FAT32 subdirectories.
+
+Better support for large flash pages with boundary alignment offsets
+for the FAT table and data region.
+
+exFAT files have two separate 64-bit length fields.  The DataLength
+field indicate how much space is allocate to the file. The ValidDataLength
+field indicates how much actual data has been written to the file.
+
+An exFAT file can be contiguous with pre-allocate clusters and bypass the
+use of the FAT table.  In this case the contiguous flag is set in the
+directory entry.  This allows an entire file to be written as one large
+multi-block write.
+
+\section SDPath Paths and Working Directories
+
+Relative paths in %SdFat are resolved in a manner similar to Windows.
+
+Each instance of SdFat32, SdExFat, and SdFs has a current directory.
+This directory is called the volume working directory, vwd.
+Initially this directory is the root directory for the volume.
+
+The volume working directory is changed by calling the chdir(path).
+
+The call sd.chdir("/2014") will change the volume working directory
+for sd to "/2014", assuming "/2014" exists.
+
+Relative paths for member functions are resolved by starting at
+the volume working directory.
+
+For example, the call sd.mkdir("April") will create the directory
+"/2014/April" assuming the volume working directory is "/2014".
+
+There is current working directory, cwd, that is used to resolve paths
+for file.open() calls.
+
+For a single SD card, the current working directory is always the volume
+working directory for that card.
+
+For multiple SD cards the current working directory is set to the volume
+working directory of a card by calling the chvol() member function.
+The chvol() call is like the Windows \<drive letter>: command.
+
+The call sd2.chvol() will set the current working directory to the volume
+working directory for sd2.
+
+If the volume working directory for sd2 is "/music" the call
+
+file.open("BigBand.wav", O_READ);
+
+will open "/music/BigBand.wav" on sd2.
+
+\section Install Installation
+
+You must manually install %SdFat by renaming the download folder %SdFat
+and copy the %SdFat folder to the Arduino libraries folder in your
+sketchbook folder.
+
+It will be necessary to unzip and rename the folder if you download a zip
+file from GitHub.
+
+See the Manual installation section of this guide.
+
+http://arduino.cc/en/Guide/Libraries
+
+\section SDconfig SdFat Configuration
+
+Several configuration options may be changed by editing the SdFatConfig.h
+file in the %SdFat/src folder.
+
+Here are a few of the key options.
+
+If the symbol ENABLE_DEDICATED_SPI is nonzero, multi-block SD I/O may
+be used for better performance.  The SPI bus may not be shared with
+other devices in this mode.
+
+The symbol SPI_DRIVER_SELECT is used to select the SPI driver.
+
+> If the symbol SPI_DRIVER_SELECT is:
+>
+> 0 - An optimized custom SPI driver is used if it exists
+>     else the standard library driver is used.
+>
+> 1 - The standard library driver is always used.
+>
+> 2 - The software SPI driver is always used.
+>
+> 3 - An external SPI driver derived from SdSpiBaseClass is always used.
+
+To enable SD card CRC checking in SPI mode set USE_SD_CRC nonzero.
+
+See SdFatConfig.h for other options.
+
+\section Hardware Hardware Configuration
+
+The hardware interface to the SD card should not use a resistor based level
+shifter. Resistor based level shifters results in signal rise times that are
+ too slow for many newer SD cards.
+
+
+\section HowTo How to format SD Cards
+
+The best way to restore an SD card's format on a PC or Mac is to use
+SDFormatter which can be downloaded from:
+
+http://www.sdcard.org/downloads
+
+A formatter program, SdFormatter.ino, is included in the
+%SdFat/examples/SdFormatter directory.  This program attempts to
+emulate SD Association's SDFormatter.
+
+SDFormatter aligns flash erase boundaries with file
+system structures which reduces write latency and file system overhead.
+
+The PC/Mac SDFormatter does not have an option for FAT type so it may format
+very small cards as FAT12.  Use the %SdFormatter example to force FAT16
+formatting of small cards.
+
+Do not format the SD card with an OS utility, OS utilities do not format SD
+cards in conformance with the SD standard.
+
+You should use a freshly formatted SD card for best performance.  FAT
+file systems become slower if many files have been created and deleted.
+This is because the directory entry for a deleted file is marked as deleted,
+but is not deleted.  When a new file is created, these entries must be scanned
+before creating the file.  Also files can become
+fragmented which causes reads and writes to be slower.
+
+\section ExampleFiles Examples
+
+A number of examples are provided in the SdFat/examples folder.
+
+To access these examples from the Arduino development environment
+go to:  %File -> Examples -> %SdFat -> \<program Name\>
+
+Compile, upload to your Arduino and click on Serial Monitor to run
+the example.
+
+Here is a list:
+
+AvrAdcLogger - Fast AVR ADC logger using Timer/ADC interrupts.
+
+BackwardCompatibility - Demonstrate SD.h compatibility with %SdFat.h.
+
+bench - A read/write benchmark.
+
+%BufferedPrint - Demo a buffered print class for AVR loggers.
+
+debug folder - Some of my debug programs - will be remove in the future.
+
+DirectoryFunctions - Use of chdir(), ls(), mkdir(), and rmdir().
+
+examplesV1 folder - Examples from SdFat V1 for compatibility tests.
+
+ExFatLogger - A data-logger optimized for exFAT features.
+
+MinimumSizeSdReader - Example of small file reader for FAT16/FAT32.
+
+OpenNext - Open all files in the root dir and print their filename.
+
+QuickStart - Quick hardware test for SPI card access.
+
+ReadCsvFile - Function to read a CSV text file one field at a time.
+
+rename - demonstrates use of rename().
+
+RtcTimestampTest - Demonstration of timestamps with RTClib.
+
+SdErrorCodes - Produce a list of error codes.
+
+SdFormatter - This program will format an SD, SDHC, or SDXC card.
+
+SdInfo - Initialize an SD card and analyze its structure for trouble shooting.
+
+SoftwareSpi - Demo of limited Software SPI support in SdFat V2.
+
+STM32Test - Example use of two SPI ports on an STM32 board.
+
+TeensyDmaAdcLogger - Fast logger using DMA ADC.
+
+TeensyRtcTimestamp - %File timestamps for Teensy3.
+
+TeensySdioDemo - Demo of SDIO and SPI modes for the Teensy 3.5/3.6 built-in SD.
+
+TeensySdioLogger -  Fast logger using a ring buffer.
+
+UnicodeFilenames - Test program for Unicode file names.
+
+UserChipSelectFunction - Useful for port expanders or replacement of the standard GPIO functions.
+
+UserSPIDriver - An example of an external SPI driver.
+ */

+ 899 - 0
lib/SdFat_NoArduino/examples/AvrAdcLogger/AvrAdcLogger.ino

@@ -0,0 +1,899 @@
+/**
+ * This program logs data from the Arduino ADC to a binary file.
+ *
+ * Samples are logged at regular intervals. Each Sample consists of the ADC
+ * values for the analog pins defined in the PIN_LIST array.  The pins numbers
+ * may be in any order.
+ *
+ * Edit the configuration constants below to set the sample pins, sample rate,
+ * and other configuration values.
+ *
+ * If your SD card has a long write latency, it may be necessary to use
+ * slower sample rates.  Using a Mega Arduino helps overcome latency
+ * problems since more 64 byte buffer blocks will be used.
+ *
+ * Each 64 byte data block in the file has a four byte header followed by up
+ * to 60 bytes of data. (60 values in 8-bit mode or 30 values in 10-bit mode)
+ * Each block contains an integral number of samples with unused space at the
+ * end of the block.
+ *
+ */
+#ifdef __AVR__
+#include <SPI.h>
+#include "SdFat.h"
+#include "BufferedPrint.h"
+#include "FreeStack.h"
+#include "AvrAdcLogger.h"
+
+// Save SRAM if 328.
+#ifdef __AVR_ATmega328P__
+#include "MinimumSerial.h"
+MinimumSerial MinSerial;
+#define Serial MinSerial
+#endif  // __AVR_ATmega328P__
+//------------------------------------------------------------------------------
+// This example was designed for exFAT but will support FAT16/FAT32.
+//
+// Note: Uno will not support SD_FAT_TYPE = 3.
+// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
+// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
+#define SD_FAT_TYPE 2
+//------------------------------------------------------------------------------
+// Set USE_RTC nonzero for file timestamps.
+// RAM use will be marginal on Uno with RTClib.
+// Set USE_RTC nonzero for file timestamps.
+// RAM use will be marginal on Uno with RTClib.
+// 0 - RTC not used
+// 1 - DS1307
+// 2 - DS3231
+// 3 - PCF8523
+#define USE_RTC 0
+#if USE_RTC
+#include "RTClib.h"
+#endif  // USE_RTC
+//------------------------------------------------------------------------------
+// Pin definitions.
+//
+// Digital pin to indicate an error, set to -1 if not used.
+// The led blinks for fatal errors. The led goes on solid for SD write
+// overrun errors and logging continues.
+const int8_t ERROR_LED_PIN = -1;
+
+// SD chip select pin.
+const uint8_t SD_CS_PIN = SS;
+//------------------------------------------------------------------------------
+// Analog pin number list for a sample.  Pins may be in any order and pin
+// numbers may be repeated.
+const uint8_t PIN_LIST[] = {0, 1, 2, 3, 4};
+//------------------------------------------------------------------------------
+// Sample rate in samples per second.
+const float SAMPLE_RATE = 5000;  // Must be 0.25 or greater.
+
+// The interval between samples in seconds, SAMPLE_INTERVAL, may be set to a
+// constant instead of being calculated from SAMPLE_RATE.  SAMPLE_RATE is not
+// used in the code below.  For example, setting SAMPLE_INTERVAL = 2.0e-4
+// will result in a 200 microsecond sample interval.
+const float SAMPLE_INTERVAL = 1.0/SAMPLE_RATE;
+
+// Setting ROUND_SAMPLE_INTERVAL non-zero will cause the sample interval to
+// be rounded to a a multiple of the ADC clock period and will reduce sample
+// time jitter.
+#define ROUND_SAMPLE_INTERVAL 1
+//------------------------------------------------------------------------------
+// Reference voltage.  See the processor data-sheet for reference details.
+// uint8_t const ADC_REF = 0; // External Reference AREF pin.
+uint8_t const ADC_REF = (1 << REFS0);  // Vcc Reference.
+// uint8_t const ADC_REF = (1 << REFS1);  // Internal 1.1 (only 644 1284P Mega)
+// uint8_t const ADC_REF = (1 << REFS1) | (1 << REFS0);  // Internal 1.1 or 2.56
+//------------------------------------------------------------------------------
+// File definitions.
+//
+// Maximum file size in bytes.
+// The program creates a contiguous file with MAX_FILE_SIZE_MiB bytes.
+// The file will be truncated if logging is stopped early.
+const uint32_t MAX_FILE_SIZE_MiB = 100;  // 100 MiB file.
+
+// log file name.  Integer field before dot will be incremented.
+#define LOG_FILE_NAME "AvrAdc00.bin"
+
+// Maximum length name including zero byte.
+const size_t NAME_DIM = 40;
+
+// Set RECORD_EIGHT_BITS non-zero to record only the high 8-bits of the ADC.
+#define RECORD_EIGHT_BITS 0
+//------------------------------------------------------------------------------
+// FIFO size definition. Use a multiple of 512 bytes for best performance.
+//
+#if RAMEND < 0X8FF
+#error SRAM too small
+#elif RAMEND < 0X10FF
+const size_t FIFO_SIZE_BYTES = 512;
+#elif RAMEND < 0X20FF
+const size_t FIFO_SIZE_BYTES = 4*512;
+#elif RAMEND < 0X40FF
+const size_t FIFO_SIZE_BYTES = 12*512;
+#else  // RAMEND
+const size_t FIFO_SIZE_BYTES = 16*512;
+#endif  // RAMEND
+//------------------------------------------------------------------------------
+// ADC clock rate.
+// The ADC clock rate is normally calculated from the pin count and sample
+// interval.  The calculation attempts to use the lowest possible ADC clock
+// rate.
+//
+// You can select an ADC clock rate by defining the symbol ADC_PRESCALER to
+// one of these values.  You must choose an appropriate ADC clock rate for
+// your sample interval.
+// #define ADC_PRESCALER 7 // F_CPU/128 125 kHz on an Uno
+// #define ADC_PRESCALER 6 // F_CPU/64  250 kHz on an Uno
+// #define ADC_PRESCALER 5 // F_CPU/32  500 kHz on an Uno
+// #define ADC_PRESCALER 4 // F_CPU/16 1000 kHz on an Uno
+// #define ADC_PRESCALER 3 // F_CPU/8  2000 kHz on an Uno (8-bit mode only)
+//==============================================================================
+// End of configuration constants.
+//==============================================================================
+// Temporary log file.  Will be deleted if a reset or power failure occurs.
+#define TMP_FILE_NAME "tmp_adc.bin"
+
+// Number of analog pins to log.
+const uint8_t PIN_COUNT = sizeof(PIN_LIST)/sizeof(PIN_LIST[0]);
+
+// Minimum ADC clock cycles per sample interval
+const uint16_t MIN_ADC_CYCLES = 15;
+
+// Extra cpu cycles to setup ADC with more than one pin per sample.
+const uint16_t ISR_SETUP_ADC = PIN_COUNT > 1 ? 100 : 0;
+
+// Maximum cycles for timer0 system interrupt.
+const uint16_t ISR_TIMER0 = 160;
+//==============================================================================
+const uint32_t MAX_FILE_SIZE = MAX_FILE_SIZE_MiB << 20;
+
+// Max SPI rate for AVR is 10 MHz for F_CPU 20 MHz, 8 MHz for F_CPU 16 MHz.
+#define SPI_CLOCK SD_SCK_MHZ(10)
+
+// Select fastest interface.
+#if ENABLE_DEDICATED_SPI
+#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
+#else  // ENABLE_DEDICATED_SPI
+#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
+#endif  // ENABLE_DEDICATED_SPI
+
+#if SD_FAT_TYPE == 0
+SdFat sd;
+typedef File file_t;
+#elif SD_FAT_TYPE == 1
+SdFat32 sd;
+typedef File32 file_t;
+#elif SD_FAT_TYPE == 2
+SdExFat sd;
+typedef ExFile file_t;
+#elif SD_FAT_TYPE == 3
+SdFs sd;
+typedef FsFile file_t;
+#else  // SD_FAT_TYPE
+#error Invalid SD_FAT_TYPE
+#endif  // SD_FAT_TYPE
+
+file_t binFile;
+file_t csvFile;
+
+char binName[] = LOG_FILE_NAME;
+
+#if RECORD_EIGHT_BITS
+const size_t BLOCK_MAX_COUNT = PIN_COUNT*(DATA_DIM8/PIN_COUNT);
+typedef block8_t block_t;
+#else  // RECORD_EIGHT_BITS
+const size_t BLOCK_MAX_COUNT = PIN_COUNT*(DATA_DIM16/PIN_COUNT);
+typedef block16_t block_t;
+#endif // RECORD_EIGHT_BITS
+
+// Size of FIFO in blocks.
+size_t const FIFO_DIM = FIFO_SIZE_BYTES/sizeof(block_t);
+block_t* fifoData;
+volatile size_t fifoCount = 0; // volatile - shared, ISR and background.
+size_t fifoHead = 0;  // Only accessed by ISR during logging.
+size_t fifoTail = 0;  // Only accessed by writer during logging.
+//==============================================================================
+// Interrupt Service Routines
+
+// Disable ADC interrupt if true.
+volatile bool isrStop = false;
+
+// Pointer to current buffer.
+block_t* isrBuf = nullptr;
+// overrun count
+uint16_t isrOver = 0;
+
+// ADC configuration for each pin.
+uint8_t adcmux[PIN_COUNT];
+uint8_t adcsra[PIN_COUNT];
+uint8_t adcsrb[PIN_COUNT];
+uint8_t adcindex = 1;
+
+// Insure no timer events are missed.
+volatile bool timerError = false;
+volatile bool timerFlag = false;
+//------------------------------------------------------------------------------
+// ADC done interrupt.
+ISR(ADC_vect) {
+  // Read ADC data.
+#if RECORD_EIGHT_BITS
+  uint8_t d = ADCH;
+#else  // RECORD_EIGHT_BITS
+  // This will access ADCL first.
+  uint16_t d = ADC;
+#endif  // RECORD_EIGHT_BITS
+
+  if (!isrBuf) {
+    if (fifoCount < FIFO_DIM) {
+      isrBuf = fifoData + fifoHead;
+    } else {
+      // no buffers - count overrun
+      if (isrOver < 0XFFFF) {
+        isrOver++;
+      }
+      // Avoid missed timer error.
+      timerFlag = false;
+      return;
+    }
+  }
+  // Start ADC for next pin
+  if (PIN_COUNT > 1) {
+    ADMUX = adcmux[adcindex];
+    ADCSRB = adcsrb[adcindex];
+    ADCSRA = adcsra[adcindex];
+    if (adcindex == 0) {
+      timerFlag = false;
+    }
+    adcindex =  adcindex < (PIN_COUNT - 1) ? adcindex + 1 : 0;
+  } else {
+    timerFlag = false;
+  }
+  // Store ADC data.
+  isrBuf->data[isrBuf->count++] = d;
+
+  // Check for buffer full.
+  if (isrBuf->count >= BLOCK_MAX_COUNT) {
+    fifoHead = fifoHead < (FIFO_DIM - 1) ? fifoHead + 1 : 0;
+    fifoCount++;
+    // Check for end logging.
+    if (isrStop) {
+      adcStop();
+      return;
+    }
+    // Set buffer needed and clear overruns.
+    isrBuf = nullptr;
+    isrOver = 0;
+  }
+}
+//------------------------------------------------------------------------------
+// timer1 interrupt to clear OCF1B
+ISR(TIMER1_COMPB_vect) {
+  // Make sure ADC ISR responded to timer event.
+  if (timerFlag) {
+    timerError = true;
+  }
+  timerFlag = true;
+}
+//==============================================================================
+// Error messages stored in flash.
+#define error(msg) (Serial.println(F(msg)),errorHalt())
+#define assert(e) ((e) ? (void)0 : error("assert: " #e))
+//------------------------------------------------------------------------------
+//
+void fatalBlink() {
+  while (true) {
+    if (ERROR_LED_PIN >= 0) {
+      digitalWrite(ERROR_LED_PIN, HIGH);
+      delay(200);
+      digitalWrite(ERROR_LED_PIN, LOW);
+      delay(200);
+    }
+  }
+}
+//------------------------------------------------------------------------------
+void errorHalt() {
+  // Print minimal error data.
+  // sd.errorPrint(&Serial);
+  // Print extended error info - uses extra bytes of flash.
+  sd.printSdError(&Serial);
+  // Try to save data.
+  binFile.close();
+  fatalBlink();
+}
+//------------------------------------------------------------------------------
+void printUnusedStack() {
+  Serial.print(F("\nUnused stack: "));
+  Serial.println(UnusedStack());
+}
+//------------------------------------------------------------------------------
+#if USE_RTC
+#if USE_RTC == 1
+RTC_DS1307 rtc;
+#elif USE_RTC == 2
+RTC_DS3231 rtc;
+#elif USE_RTC == 3
+RTC_PCF8523 rtc;
+#else  // USE_RTC == type
+#error USE_RTC type not implemented.
+#endif  // USE_RTC == type
+// Call back for file timestamps.  Only called for file create and sync().
+void dateTime(uint16_t* date, uint16_t* time, uint8_t* ms10) {
+  DateTime now = rtc.now();
+
+  // Return date using FS_DATE macro to format fields.
+  *date = FS_DATE(now.year(), now.month(), now.day());
+
+  // Return time using FS_TIME macro to format fields.
+  *time = FS_TIME(now.hour(), now.minute(), now.second());
+
+  // Return low time bits in units of 10 ms.
+  *ms10 = now.second() & 1 ? 100 : 0;
+}
+#endif  // USE_RTC
+//==============================================================================
+#if ADPS0 != 0 || ADPS1 != 1 || ADPS2 != 2
+#error unexpected ADC prescaler bits
+#endif
+//------------------------------------------------------------------------------
+inline bool adcActive() {return (1 << ADIE) & ADCSRA;}
+//------------------------------------------------------------------------------
+// initialize ADC and timer1
+void adcInit(metadata_t* meta) {
+  uint8_t adps;  // prescaler bits for ADCSRA
+  uint32_t ticks = F_CPU*SAMPLE_INTERVAL + 0.5;  // Sample interval cpu cycles.
+
+  if (ADC_REF & ~((1 << REFS0) | (1 << REFS1))) {
+    error("Invalid ADC reference");
+  }
+#ifdef ADC_PRESCALER
+  if (ADC_PRESCALER > 7 || ADC_PRESCALER < 2) {
+    error("Invalid ADC prescaler");
+  }
+  adps = ADC_PRESCALER;
+#else  // ADC_PRESCALER
+  // Allow extra cpu cycles to change ADC settings if more than one pin.
+  int32_t adcCycles = (ticks - ISR_TIMER0)/PIN_COUNT - ISR_SETUP_ADC;
+
+  for (adps = 7; adps > 0; adps--) {
+    if (adcCycles >= (MIN_ADC_CYCLES << adps)) {
+      break;
+    }
+  }
+#endif  // ADC_PRESCALER
+  meta->adcFrequency = F_CPU >> adps;
+  if (meta->adcFrequency > (RECORD_EIGHT_BITS ? 2000000 : 1000000)) {
+    error("Sample Rate Too High");
+  }
+#if ROUND_SAMPLE_INTERVAL
+  // Round so interval is multiple of ADC clock.
+  ticks += 1 << (adps - 1);
+  ticks >>= adps;
+  ticks <<= adps;
+#endif  // ROUND_SAMPLE_INTERVAL
+
+  if (PIN_COUNT > BLOCK_MAX_COUNT || PIN_COUNT > PIN_NUM_DIM) {
+    error("Too many pins");
+  }
+  meta->pinCount = PIN_COUNT;
+  meta->recordEightBits = RECORD_EIGHT_BITS;
+
+  for (int i = 0; i < PIN_COUNT; i++) {
+    uint8_t pin = PIN_LIST[i];
+    if (pin >= NUM_ANALOG_INPUTS) {
+      error("Invalid Analog pin number");
+    }
+    meta->pinNumber[i] = pin;
+
+    // Set ADC reference and low three bits of analog pin number.
+    adcmux[i] = (pin & 7) | ADC_REF;
+    if (RECORD_EIGHT_BITS) {
+      adcmux[i] |= 1 << ADLAR;
+    }
+
+    // If this is the first pin, trigger on timer/counter 1 compare match B.
+    adcsrb[i] = i == 0 ? (1 << ADTS2) | (1 << ADTS0) : 0;
+#ifdef MUX5
+    if (pin > 7) {
+      adcsrb[i] |= (1 << MUX5);
+    }
+#endif  // MUX5
+    adcsra[i] = (1 << ADEN) | (1 << ADIE) | adps;
+    // First pin triggers on timer 1 compare match B rest are free running.
+    adcsra[i] |= i == 0 ? 1 << ADATE : 1 << ADSC;
+  }
+
+  // Setup timer1
+  TCCR1A = 0;
+  uint8_t tshift;
+  if (ticks < 0X10000) {
+    // no prescale, CTC mode
+    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);
+    tshift = 0;
+  } else if (ticks < 0X10000*8) {
+    // prescale 8, CTC mode
+    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11);
+    tshift = 3;
+  } else if (ticks < 0X10000*64) {
+    // prescale 64, CTC mode
+    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11) | (1 << CS10);
+    tshift = 6;
+  } else if (ticks < 0X10000*256) {
+    // prescale 256, CTC mode
+    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS12);
+    tshift = 8;
+  } else if (ticks < 0X10000*1024) {
+    // prescale 1024, CTC mode
+    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS12) | (1 << CS10);
+    tshift = 10;
+  } else {
+    error("Sample Rate Too Slow");
+  }
+  // divide by prescaler
+  ticks >>= tshift;
+  // set TOP for timer reset
+  ICR1 = ticks - 1;
+  // compare for ADC start
+  OCR1B = 0;
+
+  // multiply by prescaler
+  ticks <<= tshift;
+
+  // Sample interval in CPU clock ticks.
+  meta->sampleInterval = ticks;
+  meta->cpuFrequency = F_CPU;
+  float sampleRate = (float)meta->cpuFrequency/meta->sampleInterval;
+  Serial.print(F("Sample pins:"));
+  for (uint8_t i = 0; i < meta->pinCount; i++) {
+    Serial.print(' ');
+    Serial.print(meta->pinNumber[i], DEC);
+  }
+  Serial.println();
+  Serial.print(F("ADC bits: "));
+  Serial.println(meta->recordEightBits ? 8 : 10);
+  Serial.print(F("ADC clock kHz: "));
+  Serial.println(meta->adcFrequency/1000);
+  Serial.print(F("Sample Rate: "));
+  Serial.println(sampleRate);
+  Serial.print(F("Sample interval usec: "));
+  Serial.println(1000000.0/sampleRate);
+}
+//------------------------------------------------------------------------------
+// enable ADC and timer1 interrupts
+void adcStart() {
+  // initialize ISR
+  adcindex = 1;
+  isrBuf = nullptr;
+  isrOver = 0;
+  isrStop = false;
+
+  // Clear any pending interrupt.
+  ADCSRA |= 1 << ADIF;
+
+  // Setup for first pin.
+  ADMUX = adcmux[0];
+  ADCSRB = adcsrb[0];
+  ADCSRA = adcsra[0];
+
+  // Enable timer1 interrupts.
+  timerError = false;
+  timerFlag = false;
+  TCNT1 = 0;
+  TIFR1 = 1 << OCF1B;
+  TIMSK1 = 1 << OCIE1B;
+}
+//------------------------------------------------------------------------------
+inline void adcStop() {
+  TIMSK1 = 0;
+  ADCSRA = 0;
+}
+//------------------------------------------------------------------------------
+// Convert binary file to csv file.
+void binaryToCsv() {
+  uint8_t lastPct = 0;
+  block_t* pd;
+  metadata_t* pm;
+  uint32_t t0 = millis();
+  // Use fast buffered print class.
+  BufferedPrint<file_t, 64> bp(&csvFile);
+  block_t binBuffer[FIFO_DIM];
+
+  assert(sizeof(block_t) == sizeof(metadata_t));
+  binFile.rewind();
+  uint32_t tPct = millis();
+  bool doMeta = true;
+  while (!Serial.available()) {
+    pd = binBuffer;
+    int nb = binFile.read(binBuffer, sizeof(binBuffer));
+    if (nb < 0) {
+      error("read binFile failed");
+    }
+    size_t nd = nb/sizeof(block_t);
+    if (nd < 1) {
+      break;
+    }
+    if (doMeta) {
+      doMeta = false;
+      pm = (metadata_t*)pd++;
+      if (PIN_COUNT != pm->pinCount) {
+        error("Invalid pinCount");
+      }
+      bp.print(F("Interval,"));
+      float intervalMicros = 1.0e6*pm->sampleInterval/(float)pm->cpuFrequency;
+      bp.print(intervalMicros, 4);
+      bp.println(F(",usec"));
+      for (uint8_t i = 0; i < PIN_COUNT; i++) {
+        if (i) {
+          bp.print(',');
+        }
+        bp.print(F("pin"));
+        bp.print(pm->pinNumber[i]);
+      }
+      bp.println();
+      if (nd-- == 1) {
+        break;
+      }
+    }
+    for (size_t i = 0; i < nd; i++, pd++) {
+      if (pd->overrun) {
+        bp.print(F("OVERRUN,"));
+        bp.println(pd->overrun);
+      }
+      for (size_t j = 0; j < pd->count; j += PIN_COUNT) {
+        for (size_t i = 0; i < PIN_COUNT; i++) {
+          if (!bp.printField(pd->data[i + j], i == (PIN_COUNT-1) ? '\n' : ',')) {
+            error("printField failed");
+          }
+        }
+      }
+    }
+    if ((millis() - tPct) > 1000) {
+      uint8_t pct = binFile.curPosition()/(binFile.fileSize()/100);
+      if (pct != lastPct) {
+        tPct = millis();
+        lastPct = pct;
+        Serial.print(pct, DEC);
+        Serial.println('%');
+      }
+    }
+  }
+  if (!bp.sync() || !csvFile.close()) {
+    error("close csvFile failed");
+  }
+  Serial.print(F("Done: "));
+  Serial.print(0.001*(millis() - t0));
+  Serial.println(F(" Seconds"));
+}
+//------------------------------------------------------------------------------
+void clearSerialInput() {
+  uint32_t m = micros();
+  do {
+    if (Serial.read() >= 0) {
+      m = micros();
+    }
+  } while (micros() - m < 10000);
+}
+//------------------------------------------------------------------------------
+void createBinFile() {
+  binFile.close();
+  while (sd.exists(binName)) {
+    char* p = strchr(binName, '.');
+    if (!p) {
+      error("no dot in filename");
+    }
+    while (true) {
+      p--;
+      if (p < binName || *p < '0' || *p > '9') {
+        error("Can't create file name");
+      }
+      if (p[0] != '9') {
+        p[0]++;
+        break;
+      }
+      p[0] = '0';
+    }
+  }
+  Serial.print(F("Opening: "));
+  Serial.println(binName);
+  if (!binFile.open(binName, O_RDWR | O_CREAT)) {
+    error("open binName failed");
+  }
+  Serial.print(F("Allocating: "));
+  Serial.print(MAX_FILE_SIZE_MiB);
+  Serial.println(F(" MiB"));
+  if (!binFile.preAllocate(MAX_FILE_SIZE)) {
+    error("preAllocate failed");
+  }
+}
+//------------------------------------------------------------------------------
+bool createCsvFile() {
+  char csvName[NAME_DIM];
+
+  if (!binFile.isOpen()) {
+    Serial.println(F("No current binary file"));
+    return false;
+  }
+  binFile.getName(csvName, sizeof(csvName));
+  char* dot = strchr(csvName, '.');
+  if (!dot) {
+    error("no dot in binName");
+  }
+  strcpy(dot + 1, "csv");
+  if (!csvFile.open(csvName, O_WRONLY|O_CREAT|O_TRUNC)) {
+    error("open csvFile failed");
+  }
+  Serial.print(F("Writing: "));
+  Serial.print(csvName);
+  Serial.println(F(" - type any character to stop"));
+  return true;
+}
+//------------------------------------------------------------------------------
+// log data
+void logData() {
+  uint32_t t0;
+  uint32_t t1;
+  uint32_t overruns =0;
+  uint32_t count = 0;
+  uint32_t maxLatencyUsec = 0;
+  size_t maxFifoUse = 0;
+  block_t fifoBuffer[FIFO_DIM];
+
+  adcInit((metadata_t*)fifoBuffer);
+  // Write metadata.
+  if (sizeof(metadata_t) != binFile.write(fifoBuffer, sizeof(metadata_t))) {
+    error("Write metadata failed");
+  }
+  fifoCount = 0;
+  fifoHead = 0;
+  fifoTail = 0;
+  fifoData = fifoBuffer;
+  // Initialize all blocks to save ISR overhead.
+  memset(fifoBuffer, 0, sizeof(fifoBuffer));
+
+  Serial.println(F("Logging - type any character to stop"));
+  // Wait for Serial Idle.
+  Serial.flush();
+  delay(10);
+
+  t0 = millis();
+  t1 = t0;
+  // Start logging interrupts.
+  adcStart();
+  while (1) {
+    uint32_t m;
+    noInterrupts();
+    size_t tmpFifoCount = fifoCount;
+    interrupts();
+    if (tmpFifoCount) {
+      block_t* pBlock = fifoData + fifoTail;
+      // Write block to SD.
+      m = micros();
+      if (sizeof(block_t) != binFile.write(pBlock, sizeof(block_t))) {
+        error("write data failed");
+      }
+      m = micros() - m;
+      t1 = millis();
+      if (m > maxLatencyUsec) {
+        maxLatencyUsec = m;
+      }
+      if (tmpFifoCount >maxFifoUse) {
+        maxFifoUse = tmpFifoCount;
+      }
+      count += pBlock->count;
+
+      // Add overruns and possibly light LED.
+      if (pBlock->overrun) {
+        overruns += pBlock->overrun;
+        if (ERROR_LED_PIN >= 0) {
+          digitalWrite(ERROR_LED_PIN, HIGH);
+        }
+      }
+      // Initialize empty block to save ISR overhead.
+      pBlock->count = 0;
+      pBlock->overrun = 0;
+      fifoTail = fifoTail < (FIFO_DIM - 1) ? fifoTail + 1 : 0;
+
+      noInterrupts();
+      fifoCount--;
+      interrupts();
+
+      if (binFile.curPosition() >= MAX_FILE_SIZE) {
+        // File full so stop ISR calls.
+        adcStop();
+        break;
+      }
+    }
+    if (timerError) {
+      error("Missed timer event - rate too high");
+    }
+    if (Serial.available()) {
+      // Stop ISR interrupts.
+      isrStop = true;
+    }
+    if (fifoCount == 0 && !adcActive()) {
+       break;
+    }
+  }
+  Serial.println();
+  // Truncate file if recording stopped early.
+  if (binFile.curPosition() < MAX_FILE_SIZE) {
+    Serial.println(F("Truncating file"));
+    Serial.flush();
+    if (!binFile.truncate()) {
+      error("Can't truncate file");
+    }
+  }
+  Serial.print(F("Max write latency usec: "));
+  Serial.println(maxLatencyUsec);
+  Serial.print(F("Record time sec: "));
+  Serial.println(0.001*(t1 - t0), 3);
+  Serial.print(F("Sample count: "));
+  Serial.println(count/PIN_COUNT);
+  Serial.print(F("Overruns: "));
+  Serial.println(overruns);
+  Serial.print(F("FIFO_DIM: "));
+  Serial.println(FIFO_DIM);
+  Serial.print(F("maxFifoUse: "));
+  Serial.println(maxFifoUse + 1);  // include ISR use.
+  Serial.println(F("Done"));
+}
+//------------------------------------------------------------------------------
+void openBinFile() {
+  char name[NAME_DIM];
+  clearSerialInput();
+  Serial.println(F("Enter file name"));
+  if (!serialReadLine(name, sizeof(name))) {
+    return;
+  }
+  if (!sd.exists(name)) {
+    Serial.println(name);
+    Serial.println(F("File does not exist"));
+    return;
+  }
+  binFile.close();
+  if (!binFile.open(name, O_RDWR)) {
+    Serial.println(name);
+    Serial.println(F("open failed"));
+    return;
+  }
+  Serial.println(F("File opened"));
+}
+//------------------------------------------------------------------------------
+// Print data file to Serial
+void printData() {
+  block_t buf;
+  if (!binFile.isOpen()) {
+    Serial.println(F("No current binary file"));
+    return;
+  }
+  binFile.rewind();
+  if (binFile.read(&buf , sizeof(buf)) != sizeof(buf)) {
+    error("Read metadata failed");
+  }
+  Serial.println(F("Type any character to stop"));
+  delay(1000);
+  while (!Serial.available() &&
+         binFile.read(&buf , sizeof(buf)) == sizeof(buf)) {
+    if (buf.count == 0) {
+      break;
+    }
+    if (buf.overrun) {
+      Serial.print(F("OVERRUN,"));
+      Serial.println(buf.overrun);
+    }
+    for (size_t i = 0; i < buf.count; i++) {
+      Serial.print(buf.data[i], DEC);
+      if ((i+1)%PIN_COUNT) {
+        Serial.print(',');
+      } else {
+        Serial.println();
+      }
+    }
+  }
+  Serial.println(F("Done"));
+}
+//------------------------------------------------------------------------------
+bool serialReadLine(char* str, size_t size) {
+  size_t n = 0;
+  while(!Serial.available()) {
+  }
+  while (true) {
+    int c = Serial.read();
+    if (c < ' ') break;
+    str[n++] = c;
+    if (n >= size) {
+      Serial.println(F("input too long"));
+      return false;
+    }
+    uint32_t m = millis();
+    while (!Serial.available() && (millis() - m) < 100){}
+    if (!Serial.available()) break;
+  }
+  str[n] = 0;
+  return true;
+}
+//------------------------------------------------------------------------------
+void setup(void) {
+  if (ERROR_LED_PIN >= 0) {
+    pinMode(ERROR_LED_PIN, OUTPUT);
+  }
+  Serial.begin(9600);
+  while(!Serial) {}
+  Serial.println(F("Type any character to begin."));
+  while(!Serial.available()) {}
+
+  FillStack();
+
+  // Read the first sample pin to init the ADC.
+  analogRead(PIN_LIST[0]);
+
+#if !ENABLE_DEDICATED_SPI
+  Serial.println(F(
+    "\nFor best performance edit SdFatConfig.h\n"
+    "and set ENABLE_DEDICATED_SPI nonzero"));
+#endif  // !ENABLE_DEDICATED_SPI
+  // Initialize SD.
+  if (!sd.begin(SD_CONFIG)) {
+    error("sd.begin failed");
+  }
+#if USE_RTC
+  if (!rtc.begin()) {
+    error("rtc.begin failed");
+  }
+  if (!rtc.isrunning()) {
+    // Set RTC to sketch compile date & time.
+    // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
+    error("RTC is NOT running!");
+  } else {
+    Serial.println(F("RTC is running"));
+  }
+  // Set callback
+  FsDateTime::setCallback(dateTime);
+#endif  // USE_RTC
+}
+//------------------------------------------------------------------------------
+void loop(void) {
+  printUnusedStack();
+  // Read any Serial data.
+  clearSerialInput();
+  Serial.println();
+  Serial.println(F("type:"));
+  Serial.println(F("b - open existing bin file"));
+  Serial.println(F("c - convert file to csv"));
+  Serial.println(F("l - list files"));
+  Serial.println(F("p - print data to Serial"));
+  Serial.println(F("r - record ADC data"));
+
+  while(!Serial.available()) {
+    yield();
+  }
+  char c = tolower(Serial.read());
+  Serial.println();
+  if (ERROR_LED_PIN >= 0) {
+    digitalWrite(ERROR_LED_PIN, LOW);
+  }
+  // Read any Serial data.
+  clearSerialInput();
+
+  if (c == 'b') {
+    openBinFile();
+  } else if (c == 'c') {
+    if (createCsvFile()) {
+      binaryToCsv();
+    }
+  } else if (c == 'l') {
+    Serial.println(F("ls:"));
+    sd.ls(&Serial, LS_DATE | LS_SIZE);
+  } else if (c == 'p') {
+    printData();
+  } else if (c == 'r') {
+    createBinFile();
+    logData();
+  } else {
+    Serial.println(F("Invalid entry"));
+  }
+}
+#else  // __AVR__
+#error This program is only for AVR.
+#endif  // __AVR__

+ 269 - 0
lib/SdFat_NoArduino/examples/SdInfo/SdInfo.ino

@@ -0,0 +1,269 @@
+/*
+ * This program attempts to initialize an SD card and analyze its structure.
+ */
+#include "SdFat.h"
+#include "sdios.h"
+/*
+  Set DISABLE_CS_PIN to disable a second SPI device.
+  For example, with the Ethernet shield, set DISABLE_CS_PIN
+  to 10 to disable the Ethernet controller.
+*/
+const int8_t DISABLE_CS_PIN = -1;
+/*
+  Change the value of SD_CS_PIN if you are using SPI
+  and your hardware does not use the default value, SS.
+  Common values are:
+  Arduino Ethernet shield: pin 4
+  Sparkfun SD shield: pin 8
+  Adafruit SD shields and modules: pin 10
+*/
+// SDCARD_SS_PIN is defined for the built-in SD on some boards.
+#ifndef SDCARD_SS_PIN
+const uint8_t SD_CS_PIN = SS;
+#else  // SDCARD_SS_PIN
+const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
+#endif  // SDCARD_SS_PIN
+
+// Try to select the best SD card configuration.
+#if HAS_SDIO_CLASS
+#define SD_CONFIG SdioConfig(FIFO_SDIO)
+#elif ENABLE_DEDICATED_SPI
+#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(16))
+#else  // HAS_SDIO_CLASS
+#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(16))
+#endif  // HAS_SDIO_CLASS
+
+//------------------------------------------------------------------------------
+SdFs sd;
+cid_t cid;
+csd_t csd;
+scr_t scr;
+uint8_t cmd6Data[64];
+uint32_t eraseSize;
+uint32_t ocr;
+static ArduinoOutStream cout(Serial);
+//------------------------------------------------------------------------------
+void cidDmp() {
+  cout << F("\nManufacturer ID: ");
+  cout << uppercase << showbase << hex << int(cid.mid) << dec << endl;
+  cout << F("OEM ID: ") << cid.oid[0] << cid.oid[1] << endl;
+  cout << F("Product: ");
+  for (uint8_t i = 0; i < 5; i++) {
+    cout << cid.pnm[i];
+  }
+  cout << F("\nRevision: ") << cid.prvN() << '.' << cid.prvM() << endl;
+  cout << F("Serial number: ") << hex << cid.psn() << dec << endl;
+  cout << F("Manufacturing date: ");
+  cout << cid.mdtMonth() << '/' << cid.mdtYear() << endl;
+  cout << endl;
+}
+//------------------------------------------------------------------------------
+void clearSerialInput() {
+  uint32_t m = micros();
+  do {
+    if (Serial.read() >= 0) {
+      m = micros();
+    }
+  } while (micros() - m < 10000);
+}
+//------------------------------------------------------------------------------
+void csdDmp() {
+  eraseSize = csd.eraseSize();
+  cout << F("cardSize: ") << 0.000512 * csd.capacity();
+  cout << F(" MB (MB = 1,000,000 bytes)\n");
+
+  cout << F("flashEraseSize: ") << int(eraseSize) << F(" blocks\n");
+  cout << F("eraseSingleBlock: ");
+  if (csd.eraseSingleBlock()) {
+    cout << F("true\n");
+  } else {
+    cout << F("false\n");
+  }
+  cout << F("dataAfterErase: ");
+  if (scr.dataAfterErase()) {
+    cout << F("ones\n");
+  } else {
+    cout << F("zeros\n");
+  }
+}
+//------------------------------------------------------------------------------
+void errorPrint() {
+  if (sd.sdErrorCode()) {
+    cout << F("SD errorCode: ") << hex << showbase;
+    printSdErrorSymbol(&Serial, sd.sdErrorCode());
+    cout << F(" = ") << int(sd.sdErrorCode()) << endl;
+    cout << F("SD errorData = ") << int(sd.sdErrorData()) << dec << endl;
+  }
+}
+//------------------------------------------------------------------------------
+bool mbrDmp() {
+  MbrSector_t mbr;
+  bool valid = true;
+  if (!sd.card()->readSector(0, (uint8_t*)&mbr)) {
+    cout << F("\nread MBR failed.\n");
+    errorPrint();
+    return false;
+  }
+  cout << F("\nSD Partition Table\n");
+  cout << F("part,boot,bgnCHS[3],type,endCHS[3],start,length\n");
+  for (uint8_t ip = 1; ip < 5; ip++) {
+    MbrPart_t *pt = &mbr.part[ip - 1];
+    if ((pt->boot != 0 && pt->boot != 0X80) ||
+        getLe32(pt->relativeSectors) > csd.capacity()) {
+      valid = false;
+    }
+    cout << int(ip) << ',' << uppercase << showbase << hex;
+    cout << int(pt->boot) << ',';
+    for (int i = 0; i < 3; i++ ) {
+      cout << int(pt->beginCHS[i]) << ',';
+    }
+    cout << int(pt->type) << ',';
+    for (int i = 0; i < 3; i++ ) {
+      cout << int(pt->endCHS[i]) << ',';
+    }
+    cout << dec << getLe32(pt->relativeSectors) << ',';
+    cout << getLe32(pt->totalSectors) << endl;
+  }
+  if (!valid) {
+    cout << F("\nMBR not valid, assuming Super Floppy format.\n");
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+void dmpVol() {
+  cout << F("\nScanning FAT, please wait.\n");
+  int32_t freeClusterCount = sd.freeClusterCount();
+  if (sd.fatType() <= 32) {
+    cout << F("\nVolume is FAT") << int(sd.fatType()) << endl;
+  } else {
+    cout << F("\nVolume is exFAT\n");
+  }
+  cout << F("sectorsPerCluster: ") << sd.sectorsPerCluster() << endl;
+  cout << F("fatStartSector:    ") << sd.fatStartSector() << endl;
+  cout << F("dataStartSector:   ") << sd.dataStartSector() << endl;
+  cout << F("clusterCount:      ") << sd.clusterCount() << endl;  
+  cout << F("freeClusterCount:  ");
+  if (freeClusterCount >= 0) {
+    cout << freeClusterCount << endl;
+  } else {
+    cout << F("failed\n");
+    errorPrint();    
+  }
+}
+//------------------------------------------------------------------------------
+void printCardType() {
+
+  cout << F("\nCard type: ");
+
+  switch (sd.card()->type()) {
+    case SD_CARD_TYPE_SD1:
+      cout << F("SD1\n");
+      break;
+
+    case SD_CARD_TYPE_SD2:
+      cout << F("SD2\n");
+      break;
+
+    case SD_CARD_TYPE_SDHC:
+      if (csd.capacity() < 70000000) {
+        cout << F("SDHC\n");
+      } else {
+        cout << F("SDXC\n");
+      }
+      break;
+
+    default:
+      cout << F("Unknown\n");
+  }
+}
+//------------------------------------------------------------------------------
+void printConfig(SdSpiConfig config) {
+  if (DISABLE_CS_PIN < 0) {
+    cout << F(
+           "\nAssuming the SD is the only SPI device.\n"
+           "Edit DISABLE_CS_PIN to disable an SPI device.\n");
+  } else {
+    cout << F("\nDisabling SPI device on pin ");
+    cout << int(DISABLE_CS_PIN) << endl;
+    pinMode(DISABLE_CS_PIN, OUTPUT);
+    digitalWrite(DISABLE_CS_PIN, HIGH);
+  }
+  cout << F("\nAssuming the SD chip select pin is: ") << int(config.csPin);
+  cout << F("\nEdit SD_CS_PIN to change the SD chip select pin.\n");
+}
+//------------------------------------------------------------------------------
+void printConfig(SdioConfig config) {
+  (void)config;
+  cout << F("Assuming an SDIO interface.\n");
+}
+//-----------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  cout << F("SdFat version: ") << SD_FAT_VERSION_STR << endl;
+  printConfig(SD_CONFIG);
+
+}
+//------------------------------------------------------------------------------
+void loop() {
+  // Read any existing Serial data.
+  clearSerialInput();
+
+  // F stores strings in flash to save RAM
+  cout << F("\ntype any character to start\n");
+  while (!Serial.available()) {
+    yield();
+  }
+  uint32_t t = millis();
+  if (!sd.cardBegin(SD_CONFIG)) {
+    cout << F(
+           "\nSD initialization failed.\n"
+           "Do not reformat the card!\n"
+           "Is the card correctly inserted?\n"
+           "Is there a wiring/soldering problem?\n");
+    if (isSpi(SD_CONFIG)) {
+      cout << F(
+           "Is SD_CS_PIN set to the correct value?\n"
+           "Does another SPI device need to be disabled?\n"
+           );
+    }
+    errorPrint();
+    return;
+  }
+  t = millis() - t;
+  cout << F("init time: ") << dec << t << " ms" << endl;
+
+  if (!sd.card()->readCID(&cid) ||
+      !sd.card()->readCSD(&csd) ||
+      !sd.card()->readOCR(&ocr) ||
+      !sd.card()->readSCR(&scr)) {
+    cout << F("readInfo failed\n");
+    errorPrint();
+    return;
+  }
+  printCardType();
+  cout << F("sdSpecVer: ") << 0.01*scr.sdSpecVer() << endl;
+  cout << F("HighSpeedMode: ");
+  if (scr.sdSpecVer() &&
+    sd.card()->cardCMD6(0X00FFFFFF, cmd6Data) && (2 & cmd6Data[13])) {
+    cout << F("true\n");
+  } else {
+    cout << F("false\n");
+  }      
+  cidDmp();
+  csdDmp();
+  cout << F("\nOCR: ") << uppercase << showbase;
+  cout << hex << ocr << dec << endl;
+  if (!mbrDmp()) {
+    return;
+  }
+  if (!sd.volumeBegin()) {
+    cout << F("\nvolumeBegin failed. Is the card formatted?\n");
+    errorPrint();
+    return;
+  }
+  dmpVol();
+}

+ 274 - 0
lib/SdFat_NoArduino/examples/bench/bench.ino

@@ -0,0 +1,274 @@
+/*
+ * This program is a simple binary write/read benchmark.
+ */
+#include "SdFat.h"
+#include "sdios.h"
+#include "FreeStack.h"
+
+// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
+// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
+#define SD_FAT_TYPE 0
+/*
+  Change the value of SD_CS_PIN if you are using SPI and
+  your hardware does not use the default value, SS.
+  Common values are:
+  Arduino Ethernet shield: pin 4
+  Sparkfun SD shield: pin 8
+  Adafruit SD shields and modules: pin 10
+*/
+// SDCARD_SS_PIN is defined for the built-in SD on some boards.
+#ifndef SDCARD_SS_PIN
+const uint8_t SD_CS_PIN = SS;
+#else  // SDCARD_SS_PIN
+// Assume built-in SD is used.
+const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
+#endif  // SDCARD_SS_PIN
+
+// Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
+#define SPI_CLOCK SD_SCK_MHZ(50)
+
+// Try to select the best SD card configuration.
+#if HAS_SDIO_CLASS
+#define SD_CONFIG SdioConfig(FIFO_SDIO)
+#elif  ENABLE_DEDICATED_SPI
+#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
+#else  // HAS_SDIO_CLASS
+#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
+#endif  // HAS_SDIO_CLASS
+
+// Set PRE_ALLOCATE true to pre-allocate file clusters.
+const bool PRE_ALLOCATE = true;
+
+// Set SKIP_FIRST_LATENCY true if the first read/write to the SD can
+// be avoid by writing a file header or reading the first record.
+const bool SKIP_FIRST_LATENCY = true;
+
+// Size of read/write.
+const size_t BUF_SIZE = 512;
+
+// File size in MB where MB = 1,000,000 bytes.
+const uint32_t FILE_SIZE_MB = 5;
+
+// Write pass count.
+const uint8_t WRITE_COUNT = 2;
+
+// Read pass count.
+const uint8_t READ_COUNT = 2;
+//==============================================================================
+// End of configuration constants.
+//------------------------------------------------------------------------------
+// File size in bytes.
+const uint32_t FILE_SIZE = 1000000UL*FILE_SIZE_MB;
+
+// Insure 4-byte alignment.
+uint32_t buf32[(BUF_SIZE + 3)/4];
+uint8_t* buf = (uint8_t*)buf32;
+
+#if SD_FAT_TYPE == 0
+SdFat sd;
+File file;
+#elif SD_FAT_TYPE == 1
+SdFat32 sd;
+File32 file;
+#elif SD_FAT_TYPE == 2
+SdExFat sd;
+ExFile file;
+#elif SD_FAT_TYPE == 3
+SdFs sd;
+FsFile file;
+#else  // SD_FAT_TYPE
+#error Invalid SD_FAT_TYPE
+#endif  // SD_FAT_TYPE
+
+// Serial output stream
+ArduinoOutStream cout(Serial);
+//------------------------------------------------------------------------------
+// Store error strings in flash to save RAM.
+#define error(s) sd.errorHalt(&Serial, F(s))
+//------------------------------------------------------------------------------
+void cidDmp() {
+  cid_t cid;
+  if (!sd.card()->readCID(&cid)) {
+    error("readCID failed");
+  }
+  cout << F("\nManufacturer ID: ");
+  cout << uppercase << showbase << hex << int(cid.mid) << dec << endl;
+  cout << F("OEM ID: ") << cid.oid[0] << cid.oid[1] << endl;
+  cout << F("Product: ");
+  for (uint8_t i = 0; i < 5; i++) {
+    cout << cid.pnm[i];
+  }
+  cout << F("\nRevision: ") << cid.prvN() << '.' << cid.prvM() << endl;
+  cout << F("Serial number: ") << hex << cid.psn() << dec << endl;
+  cout << F("Manufacturing date: ");
+  cout << cid.mdtMonth() << '/' << cid.mdtYear() << endl;
+  cout << endl;
+}
+//------------------------------------------------------------------------------
+void clearSerialInput() {
+  uint32_t m = micros();
+  do {
+    if (Serial.read() >= 0) {
+      m = micros();
+    }
+  } while (micros() - m < 10000);
+}
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  delay(1000);
+  cout << F("\nUse a freshly formatted SD for best performance.\n");
+  if (!ENABLE_DEDICATED_SPI) {
+    cout << F(
+      "\nSet ENABLE_DEDICATED_SPI nonzero in\n"
+      "SdFatConfig.h for best SPI performance.\n");
+  }
+  // use uppercase in hex and use 0X base prefix
+  cout << uppercase << showbase << endl;
+}
+//------------------------------------------------------------------------------
+void loop() {
+  float s;
+  uint32_t t;
+  uint32_t maxLatency;
+  uint32_t minLatency;
+  uint32_t totalLatency;
+  bool skipLatency;
+
+  // Discard any input.
+  clearSerialInput();
+
+  // F() stores strings in flash to save RAM
+  cout << F("Type any character to start\n");
+  while (!Serial.available()) {
+    yield();
+  }
+#if HAS_UNUSED_STACK
+  cout << F("FreeStack: ") << FreeStack() << endl;
+#endif  // HAS_UNUSED_STACK
+
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+  }
+  if (sd.fatType() == FAT_TYPE_EXFAT) {
+    cout << F("Type is exFAT") << endl;
+  } else {
+    cout << F("Type is FAT") << int(sd.fatType()) << endl;
+  }
+
+  cout << F("Card size: ") << sd.card()->sectorCount()*512E-9;
+  cout << F(" GB (GB = 1E9 bytes)") << endl;
+
+  cidDmp();
+
+  // open or create file - truncate existing file.
+  if (!file.open("bench.dat", O_RDWR | O_CREAT | O_TRUNC)) {
+    error("open failed");
+  }
+
+  // fill buf with known data
+  if (BUF_SIZE > 1) {
+    for (size_t i = 0; i < (BUF_SIZE - 2); i++) {
+      buf[i] = 'A' + (i % 26);
+    }
+    buf[BUF_SIZE-2] = '\r';
+  }
+  buf[BUF_SIZE-1] = '\n';
+
+  cout << F("FILE_SIZE_MB = ") << FILE_SIZE_MB << endl;
+  cout << F("BUF_SIZE = ") << BUF_SIZE << F(" bytes\n");
+  cout << F("Starting write test, please wait.") << endl << endl;
+
+  // do write test
+  uint32_t n = FILE_SIZE/BUF_SIZE;
+  cout <<F("write speed and latency") << endl;
+  cout << F("speed,max,min,avg") << endl;
+  cout << F("KB/Sec,usec,usec,usec") << endl;
+  for (uint8_t nTest = 0; nTest < WRITE_COUNT; nTest++) {
+    file.truncate(0);
+    if (PRE_ALLOCATE) {
+      if (!file.preAllocate(FILE_SIZE)) {
+        error("preAllocate failed");
+      }
+    }
+    maxLatency = 0;
+    minLatency = 9999999;
+    totalLatency = 0;
+    skipLatency = SKIP_FIRST_LATENCY;
+    t = millis();
+    for (uint32_t i = 0; i < n; i++) {
+      uint32_t m = micros();
+      if (file.write(buf, BUF_SIZE) != BUF_SIZE) {
+        error("write failed");
+      }
+      m = micros() - m;
+      totalLatency += m;
+      if (skipLatency) {
+        // Wait until first write to SD, not just a copy to the cache.
+        skipLatency = file.curPosition() < 512;
+      } else {
+        if (maxLatency < m) {
+          maxLatency = m;
+        }
+        if (minLatency > m) {
+          minLatency = m;
+        }
+      }
+    }
+    file.sync();
+    t = millis() - t;
+    s = file.fileSize();
+    cout << s/t <<',' << maxLatency << ',' << minLatency;
+    cout << ',' << totalLatency/n << endl;
+  }
+  cout << endl << F("Starting read test, please wait.") << endl;
+  cout << endl <<F("read speed and latency") << endl;
+  cout << F("speed,max,min,avg") << endl;
+  cout << F("KB/Sec,usec,usec,usec") << endl;
+
+  // do read test
+  for (uint8_t nTest = 0; nTest < READ_COUNT; nTest++) {
+    file.rewind();
+    maxLatency = 0;
+    minLatency = 9999999;
+    totalLatency = 0;
+    skipLatency = SKIP_FIRST_LATENCY;
+    t = millis();
+    for (uint32_t i = 0; i < n; i++) {
+      buf[BUF_SIZE-1] = 0;
+      uint32_t m = micros();
+      int32_t nr = file.read(buf, BUF_SIZE);
+      if (nr != BUF_SIZE) {
+        error("read failed");
+      }
+      m = micros() - m;
+      totalLatency += m;
+      if (buf[BUF_SIZE-1] != '\n') {
+
+        error("data check error");
+      }
+      if (skipLatency) {
+        skipLatency = false;
+      } else {
+        if (maxLatency < m) {
+          maxLatency = m;
+        }
+        if (minLatency > m) {
+          minLatency = m;
+        }
+      }
+    }
+    s = file.fileSize();
+    t = millis() - t;
+    cout << s/t <<',' << maxLatency << ',' << minLatency;
+    cout << ',' << totalLatency/n << endl;
+  }
+  cout << endl << F("Done") << endl;
+  file.close();
+  sd.end();
+}

+ 11 - 0
lib/SdFat_NoArduino/library.properties

@@ -0,0 +1,11 @@
+name=SdFat_NoArduino
+version=2.2.0
+license=MIT
+author=Bill Greiman <fat16lib@sbcglobal.net>
+maintainer=Bill Greiman <fat16lib@sbcglobal.net>
+sentence=Provides access to SD memory cards.
+paragraph=The SdFat library supports FAT16, FAT32, and exFAT file systems on Standard SD, SDHC, and SDXC cards.
+category=Data Storage
+url=https://github.com/greiman/SdFat
+repository=https://github.com/greiman/SdFat.git
+architectures=*

+ 269 - 0
lib/SdFat_NoArduino/src/BufferedPrint.h

@@ -0,0 +1,269 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef BufferedPrint_h
+#define BufferedPrint_h
+/**
+ * \file
+ * \brief Fast buffered print.
+ */
+#include "common/FmtNumber.h"
+/**
+ * \class BufferedPrint
+ * \brief Fast buffered print template.
+ */
+template<typename WriteClass, uint8_t BUF_DIM>
+class BufferedPrint {
+ public:
+  BufferedPrint() : m_wr(nullptr), m_in(0) {}
+  /** BufferedPrint constructor.
+   * \param[in] wr Print destination.
+   */
+  explicit BufferedPrint(WriteClass* wr) : m_wr(wr), m_in(0) {}
+  /** Initialize the BuffedPrint class.
+   * \param[in] wr Print destination.
+   */
+  void begin(WriteClass* wr) {
+    m_wr = wr;
+    m_in = 0;
+  }
+  /** Flush the buffer - same as sync() with no status return. */
+  void flush() {sync();}
+  /** Print a character followed by a field terminator.
+   * \param[in] c character to print.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \return true for success or false if an error occurs.
+   */
+  size_t printField(char c, char term) {
+    char buf[3];
+    char* str = buf + sizeof(buf);
+    if (term) {
+      *--str = term;
+      if (term == '\n') {
+        *--str = '\r';
+      }
+    }
+    *--str = c;
+    return write(str, buf + sizeof(buf) - str);
+  }
+  /** Print a string stored in AVR flash followed by a field terminator.
+   * \param[in] fsh string to print.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \return true for success or false if an error occurs.
+   */
+  size_t printField(const __FlashStringHelper *fsh, char term) {
+#ifdef __AVR__
+    size_t rtn = 0;
+    PGM_P p = reinterpret_cast<PGM_P>(fsh);
+    char c;
+    while ((c = pgm_read_byte(p++))) {
+      if (!write(&c, 1)) {
+        return 0;
+      }
+      rtn++;
+    }
+    if (term) {
+      char buf[2];
+      char* str = buf + sizeof(buf);
+      *--str = term;
+      if (term == '\n') {
+        *--str = '\r';
+      }
+      rtn += write(str, buf + sizeof(buf) - str);
+    }
+    return rtn;
+#else  // __AVR__
+    return printField(reinterpret_cast<const char *>(fsh), term);
+#endif  // __AVR__
+  }
+  /** Print a string followed by a field terminator.
+   * \param[in] str string to print.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \return true for success or false if an error occurs.
+   */
+  size_t printField(const char* str, char term) {
+    size_t rtn = write(str, strlen(str));
+    if (term) {
+      char buf[2];
+      char* ptr = buf + sizeof(buf);
+      *--ptr = term;
+      if (term == '\n') {
+        *--ptr = '\r';
+      }
+      rtn += write(ptr, buf + sizeof(buf) - ptr);
+    }
+    return rtn;
+  }
+  /** Print a double followed by a field terminator.
+   * \param[in] d The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \param[in] prec Number of digits after decimal point.
+   * \return true for success or false if an error occurs.
+   */
+  size_t printField(double d, char term, uint8_t prec = 2) {
+    char buf[24];
+    char* str = buf + sizeof(buf);
+    if (term) {
+      *--str = term;
+      if (term == '\n') {
+        *--str = '\r';
+      }
+    }
+    str = fmtDouble(str, d, prec, false);
+    return write(str, buf + sizeof(buf) - str);
+  }
+  /** Print a float followed by a field terminator.
+   * \param[in] f The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \param[in] prec Number of digits after decimal point.
+   * \return true for success or false if an error occurs.
+   */
+  size_t printField(float f, char term,  uint8_t prec = 2) {
+    return printField(static_cast<double>(f), term, prec);
+  }
+  /** Print an integer value for 8, 16, and 32 bit signed and unsigned types.
+   * \param[in] n The value to print.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \return true for success or false if an error occurs.
+   */
+  template<typename Type>
+  size_t printField(Type n, char term) {
+    const uint8_t DIM = sizeof(Type) <= 2 ? 8 : 13;
+    char buf[DIM];
+    char* str = buf + sizeof(buf);
+
+    if (term) {
+      *--str = term;
+      if (term == '\n') {
+        *--str = '\r';
+      }
+    }
+    Type p = n < 0 ? -n : n;
+    if (sizeof(Type) <= 2) {
+      str = fmtBase10(str, (uint16_t)p);
+    } else {
+      str = fmtBase10(str, (uint32_t)p);
+    }
+    if (n < 0) {
+      *--str = '-';
+    }
+    return write(str, buf + sizeof(buf) - str);
+  }
+  /** Print CR LF.
+   * \return true for success or false if an error occurs.
+   */
+  size_t println() {
+    char buf[2];
+    buf[0] = '\r';
+    buf[1] = '\n';
+    return write(buf, 2);
+  }
+  /** Print a double.
+   * \param[in] d The number to be printed.
+   * \param[in] prec Number of digits after decimal point.
+   * \return true for success or false if an error occurs.
+   */
+  size_t print(double d, uint8_t prec = 2) {
+    return printField(d, 0, prec);
+  }
+  /** Print a double followed by CR LF.
+   * \param[in] d The number to be printed.
+   * \param[in] prec Number of digits after decimal point.
+   * \return true for success or false if an error occurs.
+   */
+  size_t println(double d, uint8_t prec = 2) {
+    return printField(d, '\n', prec);
+  }
+  /** Print a float.
+   * \param[in] f The number to be printed.
+   * \param[in] prec Number of digits after decimal point.
+   * \return true for success or false if an error occurs.
+   */
+  size_t print(float f, uint8_t prec = 2) {
+    return printField(static_cast<double>(f), 0, prec);
+  }
+  /** Print a float followed by CR LF.
+   * \param[in] f The number to be printed.
+   * \param[in] prec Number of digits after decimal point.
+   * \return true for success or false if an error occurs.
+   */
+  size_t println(float f, uint8_t prec) {
+    return printField(static_cast<double>(f), '\n', prec);
+  }
+  /** Print character, string, or number.
+   * \param[in] v item to print.
+   * \return true for success or false if an error occurs.
+   */
+  template<typename Type>
+  size_t print(Type v) {
+    return printField(v, 0);
+  }
+  /** Print character, string, or number followed by CR LF.
+   * \param[in] v item to print.
+   * \return true for success or false if an error occurs.
+   */
+  template<typename Type>
+  size_t println(Type v) {
+    return printField(v, '\n');
+  }
+
+  /** Flush the buffer.
+   * \return true for success or false if an error occurs.
+   */
+  bool sync() {
+    if (!m_wr || m_wr->write(m_buf, m_in) != m_in) {
+      return false;
+    }
+    m_in = 0;
+    return true;
+  }
+ /** Write data to an open file.
+   * \param[in] src Pointer to the location of the data to be written.
+   *
+   * \param[in] n Number of bytes to write.
+   *
+   * \return For success write() returns the number of bytes written, always
+   * \a n.
+   */
+  size_t write(const void* src, size_t n) {
+    if ((m_in + n) > sizeof(m_buf)) {
+      if (!sync()) {
+        return 0;
+      }
+      if (n >= sizeof(m_buf)) {
+        return n == m_wr->write((const uint8_t*)src, n) ? n : 0;
+      }
+    }
+    memcpy(m_buf + m_in, src, n);
+    m_in += n;
+    return n;
+  }
+
+ private:
+  WriteClass* m_wr;
+  uint8_t m_in;
+  // Insure room for double.
+  uint8_t m_buf[BUF_DIM < 24 ? 24 : BUF_DIM];  // NOLINT
+};
+#endif  // BufferedPrint_h

+ 33 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatConfig.h

@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef ExFatConfig_h
+#define ExFatConfig_h
+#include "SdFatConfig.h"
+
+#ifndef EXFAT_READ_ONLY
+#define EXFAT_READ_ONLY 0
+#endif  // EXFAT_READ_ONLY
+
+#endif  // ExFatConfig_h

+ 621 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatDbg.cpp

@@ -0,0 +1,621 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "ExFatVolume.h"
+#include "../common/upcase.h"
+#include "ExFatLib.h"
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+//------------------------------------------------------------------------------
+static void printHex(print_t* pr, uint8_t h);
+static void printHex(print_t* pr, uint16_t val);
+static void printHex(print_t* pr, uint32_t val);
+static void printHex64(print_t* pr, uint64_t n);
+static void println64(print_t* pr, uint64_t n);
+//------------------------------------------------------------------------------
+static void dmpDirData(print_t* pr, DirGeneric_t* dir) {
+  for (uint8_t k = 0; k < 31; k++) {
+    if (k) {
+      pr->write(' ');
+    }
+    printHex(pr, dir->data[k]);
+  }
+  pr->println();
+}
+//------------------------------------------------------------------------------
+static uint16_t exFatDirChecksum(const void* dir, uint16_t checksum) {
+  const uint8_t* data = reinterpret_cast<const uint8_t*>(dir);
+  bool skip = data[0] == EXFAT_TYPE_FILE;
+  for (size_t i = 0; i < 32; i += (i == 1 && skip ? 3 : 1)) {
+    checksum = ((checksum << 15) | (checksum >> 1)) + data[i];
+  }
+  return checksum;
+}
+
+//------------------------------------------------------------------------------
+static uint16_t hashDir(DirName_t* dir, uint16_t hash) {
+  for (uint8_t i = 0; i < 30; i += 2) {
+    uint16_t u = getLe16(dir->unicode + i);
+    if (!u) {
+      break;
+    }
+  uint16_t c = toUpcase(u);
+  hash = ((hash << 15) | (hash >> 1)) + (c & 0XFF);
+  hash = ((hash << 15) | (hash >> 1)) + (c >> 8);
+  }
+  return hash;
+}
+//------------------------------------------------------------------------------
+static void printDateTime(print_t* pr,
+                          uint32_t timeDate, uint8_t ms, int8_t tz) {
+  fsPrintDateTime(pr, timeDate, ms, tz);
+  pr->println();
+}
+//------------------------------------------------------------------------------
+static void printDirBitmap(print_t* pr, DirBitmap_t* dir) {
+  pr->print(F("dirBitmap: 0x"));
+  pr->println(dir->type, HEX);
+  pr->print(F("flags: 0x"));
+  pr->println(dir->flags, HEX);
+  pr->print(F("firstCluster: "));
+  pr->println(getLe32(dir->firstCluster));
+  pr->print(F("size: "));
+  println64(pr, getLe64(dir->size));
+}
+//------------------------------------------------------------------------------
+static void printDirFile(print_t* pr, DirFile_t* dir) {
+  pr->print(F("dirFile: 0x"));
+  pr->println(dir->type, HEX);
+  pr->print(F("setCount: "));
+  pr->println(dir->setCount);
+  pr->print(F("setChecksum: 0x"));
+  pr->println(getLe16(dir->setChecksum), HEX);
+  pr->print(F("attributes: 0x"));
+  pr->println(getLe16(dir->attributes), HEX);
+  pr->print(F("createTime: "));
+  printDateTime(pr, getLe32(dir->createTime),
+                dir->createTimeMs, dir->createTimezone);
+  pr->print(F("modifyTime: "));
+  printDateTime(pr, getLe32(dir->modifyTime),
+                dir->modifyTimeMs, dir->modifyTimezone);
+  pr->print(F("accessTime: "));
+  printDateTime(pr, getLe32(dir->accessTime), 0, dir->accessTimezone);
+}
+//------------------------------------------------------------------------------
+static void printDirLabel(print_t* pr, DirLabel_t* dir) {
+  pr->print(F("dirLabel: 0x"));
+  pr->println(dir->type, HEX);
+  pr->print(F("labelLength: "));
+  pr->println(dir->labelLength);
+  pr->print(F("unicode: "));
+  for (size_t i = 0; i < dir->labelLength; i++) {
+    pr->write(dir->unicode[2*i]);
+  }
+  pr->println();
+}
+//------------------------------------------------------------------------------
+static void printDirName(print_t* pr, DirName_t* dir) {
+  pr->print(F("dirName: 0x"));
+  pr->println(dir->type, HEX);
+  pr->print(F("unicode: "));
+  for (size_t i = 0; i < 30; i += 2) {
+    uint16_t c = getLe16(dir->unicode + i);
+    if (c == 0) break;
+    if (c < 128) {
+      pr->print(static_cast<char>(c));
+    } else {
+      pr->print("0x");
+      pr->print(c, HEX);
+    }
+    pr->print(' ');
+  }
+  pr->println();
+}
+//------------------------------------------------------------------------------
+static void printDirStream(print_t* pr, DirStream_t* dir) {
+  pr->print(F("dirStream: 0x"));
+  pr->println(dir->type, HEX);
+  pr->print(F("flags: 0x"));
+  pr->println(dir->flags, HEX);
+  pr->print(F("nameLength: "));
+  pr->println(dir->nameLength);
+  pr->print(F("nameHash: 0x"));
+  pr->println(getLe16(dir->nameHash), HEX);
+  pr->print(F("validLength: "));
+  println64(pr, getLe64(dir->validLength));
+  pr->print(F("firstCluster: "));
+  pr->println(getLe32(dir->firstCluster));
+  pr->print(F("dataLength: "));
+  println64(pr, getLe64(dir->dataLength));
+}
+//------------------------------------------------------------------------------
+static void printDirUpcase(print_t* pr, DirUpcase_t* dir) {
+  pr->print(F("dirUpcase: 0x"));
+  pr->println(dir->type, HEX);
+    pr->print(F("checksum: 0x"));
+  pr->println(getLe32(dir->checksum), HEX);
+  pr->print(F("firstCluster: "));
+  pr->println(getLe32(dir->firstCluster));
+  pr->print(F("size: "));
+  println64(pr, getLe64(dir->size));
+}
+//------------------------------------------------------------------------------
+static void printExFatBoot(print_t* pr, pbs_t* pbs) {
+  BpbExFat_t* ebs = reinterpret_cast<BpbExFat_t*>(pbs->bpb);
+  pr->print(F("bpbSig: 0x"));
+  pr->println(getLe16(pbs->signature), HEX);
+  pr->print(F("FileSystemName: "));
+  pr->write(reinterpret_cast<uint8_t*>(pbs->oemName), 8);
+  pr->println();
+  for (size_t i = 0; i < sizeof(ebs->mustBeZero); i++) {
+    if (ebs->mustBeZero[i]) {
+      pr->println(F("mustBeZero error"));
+      break;
+    }
+  }
+  pr->print(F("PartitionOffset: 0x"));
+  printHex64(pr, getLe64(ebs->partitionOffset));
+  pr->print(F("VolumeLength: "));
+  println64(pr, getLe64(ebs->volumeLength));
+  pr->print(F("FatOffset: 0x"));
+  pr->println(getLe32(ebs->fatOffset), HEX);
+  pr->print(F("FatLength: "));
+  pr->println(getLe32(ebs->fatLength));
+  pr->print(F("ClusterHeapOffset: 0x"));
+  pr->println(getLe32(ebs->clusterHeapOffset), HEX);
+  pr->print(F("ClusterCount: "));
+  pr->println(getLe32(ebs->clusterCount));
+  pr->print(F("RootDirectoryCluster: "));
+  pr->println(getLe32(ebs->rootDirectoryCluster));
+  pr->print(F("VolumeSerialNumber: 0x"));
+  pr->println(getLe32(ebs->volumeSerialNumber), HEX);
+  pr->print(F("FileSystemRevision: 0x"));
+  pr->println(getLe32(ebs->fileSystemRevision), HEX);
+  pr->print(F("VolumeFlags: 0x"));
+  pr->println(getLe16(ebs->volumeFlags) , HEX);
+  pr->print(F("BytesPerSectorShift: "));
+  pr->println(ebs->bytesPerSectorShift);
+  pr->print(F("SectorsPerClusterShift: "));
+  pr->println(ebs->sectorsPerClusterShift);
+  pr->print(F("NumberOfFats: "));
+  pr->println(ebs->numberOfFats);
+  pr->print(F("DriveSelect: 0x"));
+  pr->println(ebs->driveSelect, HEX);
+  pr->print(F("PercentInUse: "));
+  pr->println(ebs->percentInUse);
+}
+//------------------------------------------------------------------------------
+static void printHex(print_t* pr, uint8_t h) {
+  if (h < 16) {
+    pr->write('0');
+  }
+  pr->print(h, HEX);
+}
+//------------------------------------------------------------------------------
+static void printHex(print_t* pr, uint16_t val) {
+  bool space = true;
+  for (uint8_t i = 0; i < 4; i++) {
+    uint8_t h = (val >> (12 - 4*i)) & 15;
+    if (h || i == 3) {
+      space = false;
+    }
+    if (space) {
+      pr->write(' ');
+    } else {
+      pr->print(h, HEX);
+    }
+  }
+}
+//------------------------------------------------------------------------------
+static void printHex(print_t* pr, uint32_t val) {
+  bool space = true;
+  for (uint8_t i = 0; i < 8; i++) {
+    uint8_t h = (val >> (28 - 4*i)) & 15;
+    if (h || i == 7) {
+      space = false;
+    }
+    if (space) {
+      pr->write(' ');
+    } else {
+      pr->print(h, HEX);
+    }
+  }
+}
+//------------------------------------------------------------------------------
+static void printHex64(print_t* pr, uint64_t n) {
+  char buf[17];
+  char *str = &buf[sizeof(buf) - 1];
+  *str = '\0';
+  do {
+    uint8_t h = n & 15;
+    *--str = h < 10 ? h + '0' : h + 'A' - 10;
+    n >>= 4;
+  } while (n);
+  pr->println(str);
+}
+//------------------------------------------------------------------------------
+static void println64(print_t* pr, uint64_t n) {
+  char buf[21];
+  char *str = &buf[sizeof(buf) - 1];
+  *str = '\0';
+  do {
+    uint64_t m = n;
+    n /= 10;
+    *--str = m - 10*n + '0';
+  } while (n);
+  pr->println(str);
+}
+//------------------------------------------------------------------------------
+static void printMbr(print_t* pr, MbrSector_t* mbr) {
+  pr->print(F("mbrSig: 0x"));
+  pr->println(getLe16(mbr->signature), HEX);
+  for (int i = 0; i < 4; i++) {
+    printHex(pr, mbr->part[i].boot);
+    pr->write(' ');
+    for (int k = 0; k < 3; k++) {
+      printHex(pr, mbr->part[i].beginCHS[k]);
+      pr->write(' ');
+    }
+    printHex(pr, mbr->part[i].type);
+    pr->write(' ');
+    for (int k = 0; k < 3; k++) {
+      printHex(pr, mbr->part[i].endCHS[k]);
+      pr->write(' ');
+    }
+    pr->print(getLe32(mbr->part[i].relativeSectors), HEX);
+    pr->print(' ');
+    pr->println(getLe32(mbr->part[i].totalSectors), HEX);
+  }
+}
+//==============================================================================
+void ExFatPartition::checkUpcase(print_t* pr) {
+  bool skip = false;
+  uint16_t u = 0;
+  uint8_t* upcase = nullptr;
+  uint32_t size = 0;
+  uint32_t sector = clusterStartSector(m_rootDirectoryCluster);
+  uint8_t* cache = dataCachePrepare(sector, FsCache::CACHE_FOR_READ);
+  if (!cache) {
+    pr->println(F("read root failed"));
+    return;
+  }
+  DirUpcase_t* dir = reinterpret_cast<DirUpcase_t*>(cache);
+
+  pr->println(F("\nChecking upcase table"));
+  for (size_t i = 0; i < 16; i++) {
+    if (dir[i].type == EXFAT_TYPE_UPCASE) {
+      sector = clusterStartSector(getLe32(dir[i].firstCluster));
+      size = getLe64(dir[i].size);
+      break;
+    }
+  }
+  if (!size) {
+    pr->println(F("upcase not found"));
+    return;
+  }
+  for (size_t i = 0; i < size/2; i++) {
+    if ((i%256) == 0) {
+      upcase = dataCachePrepare(sector++, FsCache::CACHE_FOR_READ);
+      if (!upcase) {
+        pr->println(F("read upcase failed"));
+        return;
+      }
+    }
+    uint16_t v = getLe16(&upcase[2*(i & 0XFF)]);
+    if (skip) {
+      pr->print("skip ");
+      pr->print(u);
+      pr->write(' ');
+      pr->println(v);
+    }
+    if (v == 0XFFFF) {
+      skip = true;
+    } else if (skip) {
+      for (uint16_t k = 0; k < v; k++) {
+        uint16_t x = toUpcase(u + k);
+        if (x != (u + k)) {
+          printHex(pr, (uint16_t)(u+k));
+          pr->write(',');
+          printHex(pr, x);
+          pr->println("<<<<<<<<<<<<<<<<<<<<");
+        }
+      }
+      u += v;
+      skip = false;
+    } else {
+      uint16_t x = toUpcase(u);
+      if (v != x) {
+        printHex(pr, u);
+        pr->write(',');
+        printHex(pr, x);
+        pr->write(',');
+        printHex(pr, v);
+        pr->println();
+      }
+      u++;
+    }
+  }
+  pr->println(F("Done checkUpcase"));
+}
+//------------------------------------------------------------------------------
+void ExFatPartition::dmpBitmap(print_t* pr) {
+  pr->println(F("bitmap:"));
+  dmpSector(pr, m_clusterHeapStartSector);
+}
+//------------------------------------------------------------------------------
+void ExFatPartition::dmpCluster(print_t* pr, uint32_t cluster,
+                                uint32_t offset, uint32_t count) {
+  uint32_t sector = clusterStartSector(cluster) + offset;
+  for (uint32_t i = 0; i < count; i++) {
+    pr->print(F("\nSector: "));
+    pr->println(sector + i, HEX);
+    dmpSector(pr, sector + i);
+  }
+}
+//------------------------------------------------------------------------------
+void ExFatPartition::dmpFat(print_t* pr, uint32_t start, uint32_t count) {
+  uint32_t sector = m_fatStartSector + start;
+  uint32_t cluster = 128*start;
+  pr->println(F("FAT:"));
+  for (uint32_t i = 0; i < count; i++) {
+    uint8_t* cache = dataCachePrepare(sector + i, FsCache::CACHE_FOR_READ);
+    if (!cache) {
+      pr->println(F("cache read failed"));
+      return;
+    }
+    uint32_t* fat = reinterpret_cast<uint32_t*>(cache);
+    for (size_t k = 0; k < 128; k++) {
+      if (0 == cluster%8) {
+        if (k) {
+          pr->println();
+        }
+        printHex(pr, cluster);
+      }
+      cluster++;
+      pr->write(' ');
+      printHex(pr, fat[k]);
+    }
+    pr->println();
+  }
+}
+//------------------------------------------------------------------------------
+void ExFatPartition::dmpSector(print_t* pr, uint32_t sector) {
+  uint8_t* cache = dataCachePrepare(sector, FsCache::CACHE_FOR_READ);
+  if (!cache) {
+    pr->println(F("dmpSector failed"));
+    return;
+  }
+  for (uint16_t i = 0; i < m_bytesPerSector; i++) {
+    if (i%32 == 0) {
+      if (i) {
+        pr->println();
+      }
+      printHex(pr, i);
+    }
+    pr->write(' ');
+    printHex(pr, cache[i]);
+  }
+  pr->println();
+}
+//------------------------------------------------------------------------------
+bool ExFatPartition::printDir(print_t* pr, ExFatFile* file) {
+  DirGeneric_t* dir = nullptr;
+  DirFile_t* dirFile;
+  DirStream_t* dirStream;
+  DirName_t* dirName;
+  uint16_t calcHash = 0;
+  uint16_t nameHash = 0;
+  uint16_t setChecksum = 0;
+  uint16_t calcChecksum = 0;
+  uint8_t  nameLength = 0;
+  uint8_t  setCount = 0;
+  uint8_t  nUnicode;
+
+#define RAW_ROOT
+#ifndef RAW_ROOT
+  while (1) {
+    uint8_t buf[FS_DIR_SIZE];
+    if (file->read(buf, FS_DIR_SIZE) != FS_DIR_SIZE) {
+      break;
+    }
+    dir = reinterpret_cast<DirGeneric_t*>(buf);
+#else  // RAW_ROOT
+  (void)file;
+  uint32_t nDir = 1UL << (m_sectorsPerClusterShift + 4);
+  uint32_t sector = clusterStartSector(m_rootDirectoryCluster);
+  for (uint32_t iDir = 0; iDir < nDir; iDir++) {
+    size_t i = iDir%16;
+    if (i == 0) {
+      uint8_t* cache = dataCachePrepare(sector++, FsCache::CACHE_FOR_READ);
+      if (!cache) {
+        return false;
+      }
+      dir = reinterpret_cast<DirGeneric_t*>(cache);
+    } else {
+      dir++;
+    }
+#endif  // RAW_ROOT
+    if (dir->type == EXFAT_TYPE_END_DIR) {
+      break;
+    }
+    pr->println();
+
+    switch (dir->type) {
+      case EXFAT_TYPE_BITMAP:
+        printDirBitmap(pr, reinterpret_cast<DirBitmap_t*>(dir));
+        break;
+
+      case EXFAT_TYPE_UPCASE:
+        printDirUpcase(pr, reinterpret_cast<DirUpcase_t*>(dir));
+        break;
+
+      case EXFAT_TYPE_LABEL:
+        printDirLabel(pr, reinterpret_cast<DirLabel_t*>(dir));
+        break;
+
+      case EXFAT_TYPE_FILE:
+        dirFile = reinterpret_cast<DirFile_t*>(dir);
+        printDirFile(pr, dirFile);
+        setCount = dirFile->setCount;
+        setChecksum = getLe16(dirFile->setChecksum);
+        calcChecksum = exFatDirChecksum(dir, 0);
+        break;
+
+      case EXFAT_TYPE_STREAM:
+        dirStream = reinterpret_cast<DirStream_t*>(dir);
+        printDirStream(pr, dirStream);
+        nameLength = dirStream->nameLength;
+        nameHash = getLe16(dirStream->nameHash);
+        calcChecksum = exFatDirChecksum(dir, calcChecksum);
+        setCount--;
+        calcHash = 0;
+        break;
+
+       case EXFAT_TYPE_NAME:
+        dirName = reinterpret_cast<DirName_t*>(dir);
+        printDirName(pr, dirName);
+        calcChecksum = exFatDirChecksum(dir, calcChecksum);
+        nUnicode = nameLength > 15 ? 15 : nameLength;
+        calcHash = hashDir(dirName, calcHash);
+        nameLength -= nUnicode;
+        setCount--;
+        if (nameLength == 0  || setCount == 0) {
+          pr->print(F("setChecksum: 0x"));
+          pr->print(setChecksum, HEX);
+          if (setChecksum != calcChecksum) {
+            pr->print(F(" != calcChecksum: 0x"));
+          } else {
+            pr->print(F(" == calcChecksum: 0x"));
+          }
+          pr->println(calcChecksum, HEX);
+          pr->print(F("nameHash: 0x"));
+          pr->print(nameHash, HEX);
+          if (nameHash != calcHash) {
+            pr->print(F(" != calcHash: 0x"));
+          } else {
+            pr->print(F(" == calcHash: 0x"));
+          }
+          pr->println(calcHash, HEX);
+        }
+        break;
+
+      default:
+        if (dir->type & EXFAT_TYPE_USED) {
+          pr->print(F("Unknown dirType: 0x"));
+        } else {
+          pr->print(F("Unused dirType: 0x"));
+        }
+        pr->println(dir->type, HEX);
+        dmpDirData(pr, dir);
+        break;
+    }
+  }
+  pr->println(F("Done"));
+  return true;
+}
+//------------------------------------------------------------------------------
+void ExFatPartition::printFat(print_t* pr) {
+  uint32_t next;
+  int8_t status;
+  pr->println(F("FAT:"));
+  for (uint32_t cluster = 0; cluster < 16; cluster++) {
+    status = fatGet(cluster, &next);
+    pr->print(cluster, HEX);
+    pr->write(' ');
+    if (status == 0) {
+      next = EXFAT_EOC;
+    }
+    pr->println(next, HEX);
+  }
+}
+//------------------------------------------------------------------------------
+void ExFatPartition::printUpcase(print_t* pr) {
+  uint8_t* upcase = nullptr;
+  uint32_t sector;
+  uint32_t size = 0;
+  uint32_t checksum = 0;
+  DirUpcase_t* dir;
+  sector = clusterStartSector(m_rootDirectoryCluster);
+  upcase = dataCachePrepare(sector, FsCache::CACHE_FOR_READ);
+  dir = reinterpret_cast<DirUpcase_t*>(upcase);
+  if (!dir) {
+    pr->println(F("read root dir failed"));
+    return;
+  }
+  for (size_t i = 0; i < 16; i++) {
+    if (dir[i].type == EXFAT_TYPE_UPCASE) {
+      sector = clusterStartSector(getLe32(dir[i].firstCluster));
+      size = getLe64(dir[i].size);
+      break;
+    }
+  }
+  if (!size) {
+    pr->println(F("upcase not found"));
+    return;
+  }
+  for (uint16_t i = 0; i < size/2; i++) {
+    if ((i%256) == 0) {
+      upcase = dataCachePrepare(sector++, FsCache::CACHE_FOR_READ);
+      if (!upcase) {
+        pr->println(F("read upcase failed"));
+        return;
+      }
+    }
+    if (i%16 == 0) {
+      pr->println();
+      printHex(pr, i);
+    }
+    pr->write(' ');
+    uint16_t uc = getLe16(&upcase[2*(i & 0XFF)]);
+    printHex(pr, uc);
+    checksum = upcaseChecksum(uc, checksum);
+  }
+  pr->println();
+  pr->print(F("checksum: "));
+  printHex(pr, checksum);
+  pr->println();
+}
+//------------------------------------------------------------------------------
+bool ExFatPartition::printVolInfo(print_t* pr) {
+  uint8_t* cache = dataCachePrepare(0, FsCache::CACHE_FOR_READ);
+  if (!cache) {
+    pr->println(F("read mbr failed"));
+    return false;
+  }
+  MbrSector_t* mbr = reinterpret_cast<MbrSector_t*>(cache);
+  printMbr(pr, mbr);
+  uint32_t volStart = getLe32(mbr->part->relativeSectors);
+  uint32_t volSize = getLe32(mbr->part->totalSectors);
+  if (volSize == 0) {
+    pr->print(F("bad partition size"));
+    return false;
+  }
+  cache = dataCachePrepare(volStart, FsCache::CACHE_FOR_READ);
+  if (!cache) {
+    pr->println(F("read pbs failed"));
+    return false;
+  }
+  printExFatBoot(pr, reinterpret_cast<pbs_t*>(cache));
+  return true;
+}
+#endif  // DOXYGEN_SHOULD_SKIP_THIS

+ 738 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatFile.cpp

@@ -0,0 +1,738 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "ExFatFile.cpp"
+#include "../common/DebugMacros.h"
+#include "../common/FsUtf.h"
+#include "ExFatLib.h"
+//------------------------------------------------------------------------------
+/** test for legal character.
+ *
+ * \param[in] c character to be tested.
+ *
+ * \return true for legal character else false.
+ */
+inline bool lfnLegalChar(uint8_t c) {
+#if USE_UTF8_LONG_NAMES
+  return !lfnReservedChar(c);
+#else  // USE_UTF8_LONG_NAMES
+  return !(lfnReservedChar(c) || c & 0X80);
+#endif  // USE_UTF8_LONG_NAMES
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::attrib(uint8_t bits) {
+  if (!isFileOrSubDir() || (bits & FS_ATTRIB_USER_SETTABLE) != bits) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Don't allow read-only to be set if the file is open for write.
+  if ((bits & FS_ATTRIB_READ_ONLY) && isWritable()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  m_attributes = (m_attributes & ~FS_ATTRIB_USER_SETTABLE) | bits;
+  // insure sync() will update dir entry
+  m_flags |= FILE_FLAG_DIR_DIRTY;
+  if (!sync()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+uint8_t* ExFatFile::dirCache(uint8_t set, uint8_t options) {
+  DirPos_t pos = m_dirPos;
+  if (m_vol->dirSeek(&pos, FS_DIR_SIZE*set) != 1) {
+    return nullptr;
+  }
+  return m_vol->dirCache(&pos, options);
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::close() {
+  bool rtn = sync();
+  m_attributes = FILE_ATTR_CLOSED;
+  m_flags = 0;
+  return rtn;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::contiguousRange(uint32_t* bgnSector, uint32_t* endSector) {
+  if (!isContiguous()) {
+    return false;
+  }
+  if (bgnSector) {
+    *bgnSector = firstSector();
+  }
+  if (endSector) {
+    *endSector = firstSector() +
+                 ((m_validLength - 1) >> m_vol->bytesPerSectorShift());
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+void ExFatFile::fgetpos(fspos_t* pos) const {
+  pos->position = m_curPosition;
+  pos->cluster = m_curCluster;
+}
+//------------------------------------------------------------------------------
+int ExFatFile::fgets(char* str, int num, char* delim) {
+  char ch;
+  int n = 0;
+  int r = -1;
+  while ((n + 1) < num && (r = read(&ch, 1)) == 1) {
+    // delete CR
+    if (ch == '\r') {
+      continue;
+    }
+    str[n++] = ch;
+    if (!delim) {
+      if (ch == '\n') {
+        break;
+      }
+    } else {
+      if (strchr(delim, ch)) {
+        break;
+      }
+    }
+  }
+  if (r < 0) {
+    // read error
+    return -1;
+  }
+  str[n] = '\0';
+  return n;
+}
+//------------------------------------------------------------------------------
+uint32_t ExFatFile::firstSector() const {
+  return m_firstCluster ? m_vol->clusterStartSector(m_firstCluster) : 0;
+}
+//------------------------------------------------------------------------------
+void ExFatFile::fsetpos(const fspos_t* pos) {
+  m_curPosition = pos->position;
+  m_curCluster = pos->cluster;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::getAccessDateTime(uint16_t* pdate, uint16_t* ptime) {
+  DirFile_t* df = reinterpret_cast<DirFile_t*>
+                 (m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ));
+  if (!df) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  *pdate = getLe16(df->accessDate);
+  *ptime = getLe16(df->accessTime);
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::getCreateDateTime(uint16_t* pdate, uint16_t* ptime) {
+  DirFile_t* df = reinterpret_cast<DirFile_t*>
+                 (m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ));
+  if (!df) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  *pdate = getLe16(df->createDate);
+  *ptime = getLe16(df->createTime);
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::getModifyDateTime(uint16_t* pdate, uint16_t* ptime) {
+  DirFile_t* df = reinterpret_cast<DirFile_t*>
+                 (m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ));
+  if (!df) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  *pdate = getLe16(df->modifyDate);
+  *ptime = getLe16(df->modifyTime);
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::isBusy() {
+  return m_vol->isBusy();
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::open(const char* path, oflag_t oflag) {
+  return open(ExFatVolume::cwv(), path, oflag);
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::open(ExFatVolume* vol, const char* path, oflag_t oflag) {
+  return vol && open(vol->vwd(), path, oflag);
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::open(ExFatFile* dirFile, const char* path, oflag_t oflag) {
+  ExFatFile tmpDir;
+  ExName_t fname;
+  // error if already open
+  if (isOpen() || !dirFile->isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (isDirSeparator(*path)) {
+    while (isDirSeparator(*path)) {
+      path++;
+    }
+    if (*path == 0) {
+      return openRoot(dirFile->m_vol);
+    }
+    if (!tmpDir.openRoot(dirFile->m_vol)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    dirFile = &tmpDir;
+  }
+  while (1) {
+    if (!parsePathName(path, &fname, &path)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (*path == 0) {
+      break;
+    }
+    if (!openPrivate(dirFile, &fname, O_RDONLY)) {
+      DBG_WARN_MACRO;
+      goto fail;
+    }
+    tmpDir = *this;
+    dirFile = &tmpDir;
+    close();
+  }
+  return openPrivate(dirFile, &fname, oflag);
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::open(uint32_t index, oflag_t oflag) {
+  ExFatVolume* vol = ExFatVolume::cwv();
+  return vol ? open(vol->vwd(), index, oflag) : false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::open(ExFatFile* dirFile, uint32_t index, oflag_t oflag) {
+  if (dirFile->seekSet(FS_DIR_SIZE*index) && openNext(dirFile, oflag)) {
+    if (dirIndex() == index) {
+      return true;
+    }
+    close();
+    DBG_FAIL_MACRO;
+  }
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::openCwd() {
+  if (isOpen() || !ExFatVolume::cwv()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  *this = *ExFatVolume::cwv()->vwd();
+  rewind();
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::openNext(ExFatFile* dir, oflag_t oflag) {
+  if (isOpen() || !dir->isDir() || (dir->curPosition() & 0X1F)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return openPrivate(dir, nullptr, oflag);
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::openPrivate(ExFatFile* dir, ExName_t* fname, oflag_t oflag) {
+  int n;
+  uint8_t modeFlags;
+  uint32_t curCluster __attribute__((unused));
+  uint8_t* cache __attribute__((unused));
+  DirPos_t freePos __attribute__((unused));
+
+  DirFile_t*   dirFile;
+  DirStream_t* dirStream;
+  DirName_t*   dirName;
+  uint8_t buf[FS_DIR_SIZE];
+  uint8_t freeCount = 0;
+  uint8_t freeNeed = 3;
+  bool inSet = false;
+
+  // error if already open, no access mode, or no directory.
+  if (isOpen() || !dir->isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+
+  switch (oflag & O_ACCMODE) {
+    case O_RDONLY:
+      modeFlags = FILE_FLAG_READ;
+      break;
+    case O_WRONLY:
+      modeFlags = FILE_FLAG_WRITE;
+      break;
+    case O_RDWR:
+      modeFlags = FILE_FLAG_READ | FILE_FLAG_WRITE;
+      break;
+    default:
+      DBG_FAIL_MACRO;
+      goto fail;
+  }
+  modeFlags |= oflag & O_APPEND ? FILE_FLAG_APPEND : 0;
+
+  if (fname) {
+    freeNeed = 2 + (fname->nameLength + 14)/15;
+    dir->rewind();
+  }
+
+  while (1) {
+    n = dir->read(buf, FS_DIR_SIZE);
+    if (n == 0) {
+      goto create;
+    }
+    if (n != FS_DIR_SIZE) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (!(buf[0] & EXFAT_TYPE_USED)) {
+      // Unused entry.
+      if (freeCount == 0) {
+        freePos.position = dir->curPosition() - FS_DIR_SIZE;
+        freePos.cluster = dir->curCluster();
+      }
+      if (freeCount < freeNeed) {
+        freeCount++;
+      }
+      if (buf[0] == EXFAT_TYPE_END_DIR) {
+        if (fname) {
+          goto create;
+        }
+        // Likely openNext call.
+        DBG_WARN_MACRO;
+        goto fail;
+      }
+      inSet = false;
+    } else if (!inSet) {
+      if (freeCount < freeNeed) {
+        freeCount = 0;
+      }
+      if (buf[0] != EXFAT_TYPE_FILE) {
+        continue;
+      }
+      inSet = true;
+      memset(this, 0, sizeof(ExFatFile));
+      dirFile = reinterpret_cast<DirFile_t*>(buf);
+      m_setCount = dirFile->setCount;
+      m_attributes = getLe16(dirFile->attributes) & FS_ATTRIB_COPY;
+      if (!(m_attributes & FS_ATTRIB_DIRECTORY)) {
+        m_attributes |= FILE_ATTR_FILE;
+      }
+      m_vol = dir->volume();
+      m_dirPos.cluster = dir->curCluster();
+      m_dirPos.position = dir->curPosition() - FS_DIR_SIZE;
+      m_dirPos.isContiguous = dir->isContiguous();
+    } else if (buf[0] == EXFAT_TYPE_STREAM) {
+      dirStream = reinterpret_cast<DirStream_t*>(buf);
+      m_flags = modeFlags;
+      if (dirStream->flags & EXFAT_FLAG_CONTIGUOUS) {
+        m_flags |= FILE_FLAG_CONTIGUOUS;
+      }
+      m_validLength = getLe64(dirStream->validLength);
+      m_firstCluster = getLe32(dirStream->firstCluster);
+      m_dataLength = getLe64(dirStream->dataLength);
+      if (!fname) {
+        goto found;
+      }
+      fname->reset();
+      if (fname->nameLength != dirStream->nameLength ||
+          fname->nameHash != getLe16(dirStream->nameHash)) {
+        inSet = false;
+      }
+    } else if (buf[0] == EXFAT_TYPE_NAME) {
+      dirName = reinterpret_cast<DirName_t*>(buf);
+      if (!cmpName(dirName, fname)) {
+        inSet = false;
+        continue;
+      }
+      if (fname->atEnd()) {
+        goto found;
+      }
+    } else {
+      inSet = false;
+    }
+  }
+
+ found:
+  // Don't open if create only.
+  if (oflag & O_EXCL) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Write, truncate, or at end is an error for a directory or read-only file.
+  if ((oflag & (O_TRUNC | O_AT_END)) || (m_flags & FILE_FLAG_WRITE)) {
+    if (isSubDir() || isReadOnly() || EXFAT_READ_ONLY) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+
+#if !EXFAT_READ_ONLY
+  if (oflag & O_TRUNC) {
+    if (!(m_flags & FILE_FLAG_WRITE)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (!truncate(0)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  } else if ((oflag & O_AT_END) && !seekSet(fileSize())) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (isWritable()) {
+    m_attributes |= FS_ATTRIB_ARCHIVE;
+  }
+#endif  // !EXFAT_READ_ONLY
+  return true;
+
+ create:
+#if EXFAT_READ_ONLY
+  DBG_FAIL_MACRO;
+  goto fail;
+#else  // EXFAT_READ_ONLY
+  // don't create unless O_CREAT and write
+  if (!(oflag & O_CREAT) || !(modeFlags & FILE_FLAG_WRITE) || !fname) {
+    DBG_WARN_MACRO;
+    goto fail;
+  }
+  while (freeCount < freeNeed) {
+    n = dir->read(buf, FS_DIR_SIZE);
+    if (n == 0) {
+      curCluster = dir->m_curCluster;
+      if (!dir->addDirCluster()) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      dir->m_curCluster = curCluster;
+      continue;
+    }
+    if (n != FS_DIR_SIZE) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (freeCount == 0) {
+      freePos.position = dir->curPosition() - FS_DIR_SIZE;
+      freePos.cluster = dir->curCluster();
+    }
+    freeCount++;
+  }
+  freePos.isContiguous = dir->isContiguous();
+  memset(this, 0, sizeof(ExFatFile));
+  m_vol = dir->volume();
+  m_attributes = FILE_ATTR_FILE | FS_ATTRIB_ARCHIVE;
+  m_dirPos = freePos;
+  fname->reset();
+  for (uint8_t i = 0; i < freeNeed; i++) {
+    cache = dirCache(i, FsCache::CACHE_FOR_WRITE);
+    if (!cache || (cache[0] & 0x80)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    memset(cache, 0 , FS_DIR_SIZE);
+    if (i == 0) {
+      dirFile = reinterpret_cast<DirFile_t*>(cache);
+      dirFile->type = EXFAT_TYPE_FILE;
+      m_setCount = freeNeed - 1;
+      dirFile->setCount = m_setCount;
+
+      if (FsDateTime::callback) {
+        uint16_t date, time;
+        uint8_t ms10;
+        FsDateTime::callback(&date, &time, &ms10);
+        setLe16(dirFile->createDate, date);
+        setLe16(dirFile->createTime, time);
+        dirFile->createTimeMs = ms10;
+      } else {
+        setLe16(dirFile->createDate, FS_DEFAULT_DATE);
+        setLe16(dirFile->modifyDate, FS_DEFAULT_DATE);
+        setLe16(dirFile->accessDate, FS_DEFAULT_DATE);
+       if (FS_DEFAULT_TIME) {
+         setLe16(dirFile->createTime, FS_DEFAULT_TIME);
+         setLe16(dirFile->modifyTime, FS_DEFAULT_TIME);
+         setLe16(dirFile->accessTime, FS_DEFAULT_TIME);
+       }
+      }
+    } else if (i == 1) {
+      dirStream = reinterpret_cast<DirStream_t*>(cache);
+      dirStream->type = EXFAT_TYPE_STREAM;
+      dirStream->flags = EXFAT_FLAG_ALWAYS1;
+      m_flags = modeFlags | FILE_FLAG_DIR_DIRTY;
+      dirStream->nameLength = fname->nameLength;
+      setLe16(dirStream->nameHash, fname->nameHash);
+    } else {
+      dirName = reinterpret_cast<DirName_t*>(cache);
+      dirName->type = EXFAT_TYPE_NAME;
+      for (size_t k = 0; k < 15; k++) {
+        if (fname->atEnd()) {
+          break;
+        }
+        uint16_t u = fname->get16();
+        setLe16(dirName->unicode + 2*k, u);
+      }
+    }
+  }
+  return sync();
+#endif  // EXFAT_READ_ONLY
+
+ fail:
+  // close file
+  m_attributes = FILE_ATTR_CLOSED;
+  m_flags = 0;
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::openRoot(ExFatVolume* vol) {
+  if (isOpen()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  memset(this, 0, sizeof(ExFatFile));
+  m_attributes = FILE_ATTR_ROOT;
+  m_vol = vol;
+  m_flags = FILE_FLAG_READ;
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::parsePathName(const char* path,
+                            ExName_t* fname, const char** ptr) {
+  // Skip leading spaces.
+  while (*path == ' ') {
+    path++;
+  }
+  fname->begin = path;
+  fname->end = path;
+  while (*path && !isDirSeparator(*path)) {
+    uint8_t c = *path++;
+    if (!lfnLegalChar(c)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (c != '.' && c != ' ') {
+      // Need to trim trailing dots spaces.
+      fname->end = path;
+    }
+  }
+  // Advance to next path component.
+  for (; *path == ' ' || isDirSeparator(*path); path++) {}
+  *ptr = path;
+  return hashName(fname);
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+int ExFatFile::peek() {
+  uint64_t curPosition = m_curPosition;
+  uint32_t curCluster = m_curCluster;
+  int c = read();
+  m_curPosition = curPosition;
+  m_curCluster = curCluster;
+  return c;
+}
+//------------------------------------------------------------------------------
+int ExFatFile::read(void* buf, size_t count) {
+  uint8_t* dst = reinterpret_cast<uint8_t*>(buf);
+  int8_t fg;
+  size_t toRead = count;
+  size_t n;
+  uint8_t* cache;
+  uint16_t sectorOffset;
+  uint32_t sector;
+  uint32_t clusterOffset;
+
+  if (!isReadable()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (isContiguous() || isFile()) {
+    if ((m_curPosition + count) > m_validLength) {
+      count = toRead = m_validLength - m_curPosition;
+    }
+  }
+  while (toRead) {
+    clusterOffset = m_curPosition & m_vol->clusterMask();
+    sectorOffset = clusterOffset & m_vol->sectorMask();
+    if (clusterOffset == 0) {
+      if (m_curPosition == 0) {
+        m_curCluster = isRoot()
+                       ? m_vol->rootDirectoryCluster() : m_firstCluster;
+      } else if (isContiguous()) {
+        m_curCluster++;
+      } else {
+        fg = m_vol->fatGet(m_curCluster, &m_curCluster);
+        if (fg < 0) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+        if (fg == 0) {
+          // EOF if directory.
+          if (isDir()) {
+            break;
+          }
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+      }
+    }
+    sector = m_vol->clusterStartSector(m_curCluster) +
+             (clusterOffset >> m_vol->bytesPerSectorShift());
+    if (sectorOffset != 0 || toRead < m_vol->bytesPerSector()
+                          || sector == m_vol->dataCacheSector()) {
+      n = m_vol->bytesPerSector() - sectorOffset;
+      if (n > toRead) {
+        n = toRead;
+      }
+      // read sector to cache and copy data to caller
+      cache = m_vol->dataCachePrepare(sector, FsCache::CACHE_FOR_READ);
+      if (!cache) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      uint8_t* src = cache + sectorOffset;
+      memcpy(dst, src, n);
+#if USE_MULTI_SECTOR_IO
+    } else if (toRead >= 2*m_vol->bytesPerSector()) {
+      uint32_t ns = toRead >> m_vol->bytesPerSectorShift();
+      // Limit reads to current cluster.
+      uint32_t maxNs = m_vol->sectorsPerCluster()
+                       - (clusterOffset >> m_vol->bytesPerSectorShift());
+      if (ns > maxNs) {
+        ns = maxNs;
+      }
+      n = ns << m_vol->bytesPerSectorShift();
+     if (!m_vol->cacheSafeRead(sector, dst, ns)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+#endif  // USE_MULTI_SECTOR_IO
+    } else {
+      // read single sector
+      n = m_vol->bytesPerSector();
+      if (!m_vol->cacheSafeRead(sector, dst)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+    dst += n;
+    m_curPosition += n;
+    toRead -= n;
+  }
+  return count - toRead;
+
+ fail:
+  m_error |= READ_ERROR;
+  return -1;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::remove(const char* path) {
+  ExFatFile file;
+  if (!file.open(this, path, O_WRONLY)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return file.remove();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::seekSet(uint64_t pos) {
+  uint32_t nCur;
+  uint32_t nNew;
+  uint32_t tmp = m_curCluster;
+  // error if file not open
+  if (!isOpen()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Optimize O_APPEND writes.
+  if (pos == m_curPosition) {
+    return true;
+  }
+  if (pos == 0) {
+    // set position to start of file
+    m_curCluster = 0;
+    goto done;
+  }
+  if (isFile()) {
+    if (pos > m_validLength) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+  // calculate cluster index for new position
+  nNew = (pos - 1) >> m_vol->bytesPerClusterShift();
+  if (isContiguous()) {
+    m_curCluster = m_firstCluster + nNew;
+    goto done;
+  }
+  // calculate cluster index for current position
+  nCur = (m_curPosition - 1) >> m_vol->bytesPerClusterShift();
+  if (nNew < nCur || m_curPosition == 0) {
+    // must follow chain from first cluster
+    m_curCluster = isRoot() ? m_vol->rootDirectoryCluster() : m_firstCluster;
+  } else {
+    // advance from curPosition
+    nNew -= nCur;
+  }
+  while (nNew--) {
+    if (m_vol->fatGet(m_curCluster, &m_curCluster) <= 0) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+
+ done:
+  m_curPosition = pos;
+  return true;
+
+ fail:
+  m_curCluster = tmp;
+  return false;
+}

+ 864 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatFile.h

@@ -0,0 +1,864 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef ExFatFile_h
+#define ExFatFile_h
+/**
+ * \file
+ * \brief ExFatFile class
+ */
+#include <limits.h>
+#include <string.h>
+#include "../common/FsDateTime.h"
+#include "../common/FsApiConstants.h"
+#include "../common/FmtNumber.h"
+#include "../common/FsName.h"
+#include "ExFatPartition.h"
+
+class ExFatVolume;
+//------------------------------------------------------------------------------
+/** Expression for path name separator. */
+#define isDirSeparator(c) ((c) == '/')
+//------------------------------------------------------------------------------
+/**
+ * \class ExName_t
+ * \brief Internal type for file name - do not use in user apps.
+ */
+class ExName_t : public FsName {
+ public:
+  /** Length of UTF-16 name */
+  size_t nameLength;
+  /** Hash for UTF-16 name */
+  uint16_t nameHash;
+};
+//------------------------------------------------------------------------------
+/**
+ * \class ExFatFile
+ * \brief Basic file class.
+ */
+class ExFatFile {
+ public:
+  /** Create an instance. */
+  ExFatFile() {}
+  /**  Create a file object and open it in the current working directory.
+   *
+   * \param[in] path A path for a file to be opened.
+   *
+   * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
+   * OR of open flags. see FatFile::open(FatFile*, const char*, uint8_t).
+   */
+  ExFatFile(const char* path, oflag_t oflag) {
+    open(path, oflag);
+  }
+
+#if DESTRUCTOR_CLOSES_FILE
+  ~ExFatFile() {
+    if (isOpen()) {
+      close();
+    }
+  }
+#endif  // DESTRUCTOR_CLOSES_FILE
+
+  /** The parenthesis operator.
+    *
+    * \return true if a file is open.
+    */
+  operator bool() {
+    return isOpen();
+  }
+  /**
+   * \return user settable file attributes for success else -1.
+   */
+  int attrib() {
+    return isFileOrSubDir() ? m_attributes & FS_ATTRIB_COPY : -1;
+  }
+  /** Set file attributes
+   *
+   * \param[in] bits bit-wise or of selected attributes: FS_ATTRIB_READ_ONLY,
+   *            FS_ATTRIB_HIDDEN, FS_ATTRIB_SYSTEM, FS_ATTRIB_ARCHIVE.
+   *
+   * \note attrib() will fail for set read-only if the file is open for write.
+   * \return true for success or false for failure.
+   */
+  bool attrib(uint8_t bits);
+  /** \return The number of bytes available from the current position
+   * to EOF for normal files.  INT_MAX is returned for very large files.
+   *
+   * available64() is recommended for very large files.
+   *
+   * Zero is returned for directory files.
+   *
+   */
+  int available() {
+    uint64_t n = available64();
+    return n > INT_MAX ? INT_MAX : n;
+  }
+  /** \return The number of bytes available from the current position
+   * to EOF for normal files.  Zero is returned for directory files.
+   */
+  uint64_t available64() {
+    return isFile() ? fileSize() - curPosition() : 0;
+  }
+  /** Clear all error bits. */
+  void clearError() {
+    m_error = 0;
+  }
+  /** Clear writeError. */
+  void clearWriteError() {
+    m_error &= ~WRITE_ERROR;
+  }
+  /** Close a file and force cached data and directory information
+   *  to be written to the storage device.
+   *
+   * \return true for success or false for failure.
+   */
+  bool close();
+  /** Check for contiguous file and return its raw sector range.
+   *
+   * \param[out] bgnSector the first sector address for the file.
+   * \param[out] endSector the last sector address for the file.
+   *
+   * Parameters may be nullptr.
+   *
+   * \return true for success or false for failure.
+   */
+  bool contiguousRange(uint32_t* bgnSector, uint32_t* endSector);
+  /** \return The current cluster number for a file or directory. */
+  uint32_t curCluster() const {return m_curCluster;}
+  /** \return The current position for a file or directory. */
+  uint64_t curPosition() const {return m_curPosition;}
+  /** \return Total data length for file. */
+  uint64_t dataLength() const {return m_dataLength;}
+  /** \return Directory entry index. */
+  uint32_t dirIndex() const {return m_dirPos.position/FS_DIR_SIZE;}
+  /** Test for the existence of a file in a directory
+   *
+   * \param[in] path Path of the file to be tested for.
+   *
+   * The calling instance must be an open directory file.
+   *
+   * dirFile.exists("TOFIND.TXT") searches for "TOFIND.TXT" in the directory
+   * dirFile.
+   *
+   * \return true if the file exists else false.
+   */
+    bool exists(const char* path) {
+      ExFatFile file;
+      return file.open(this, path, O_RDONLY);
+    }
+  /** get position for streams
+   * \param[out] pos struct to receive position
+   */
+  void fgetpos(fspos_t* pos) const;
+ /**
+   * Get a string from a file.
+   *
+   * fgets() reads bytes from a file into the array pointed to by \a str, until
+   * \a num - 1 bytes are read, or a delimiter is read and transferred to
+   * \a str, or end-of-file is encountered. The string is then terminated
+   * with a null byte.
+   *
+   * fgets() deletes CR, '\\r', from the string.  This insures only a '\\n'
+   * terminates the string for Windows text files which use CRLF for newline.
+   *
+   * \param[out] str Pointer to the array where the string is stored.
+   * \param[in] num Maximum number of characters to be read
+   * (including the final null byte). Usually the length
+   * of the array \a str is used.
+   * \param[in] delim Optional set of delimiters. The default is "\n".
+   *
+   * \return For success fgets() returns the length of the string in \a str.
+   * If no data is read, fgets() returns zero for EOF or -1 if an error
+   * occurred.
+   */
+  int fgets(char* str, int num, char* delim = nullptr);
+  /** \return The total number of bytes in a file. */
+  uint64_t fileSize() const {return m_validLength;}
+  /** \return Address of first sector or zero for empty file. */
+  uint32_t firstSector() const;
+  /** Set position for streams
+   * \param[in] pos struct with value for new position
+   */
+  void fsetpos(const fspos_t* pos);
+  /** Arduino name for sync() */
+  void flush() {sync();}
+  /** Get a file's access date and time.
+   *
+   * \param[out] pdate Packed date for directory entry.
+   * \param[out] ptime Packed time for directory entry.
+   *
+   * \return true for success or false for failure.
+   */
+  bool getAccessDateTime(uint16_t* pdate, uint16_t* ptime);
+  /** Get a file's create date and time.
+   *
+   * \param[out] pdate Packed date for directory entry.
+   * \param[out] ptime Packed time for directory entry.
+   *
+   * \return true for success or false for failure.
+   */
+  bool getCreateDateTime(uint16_t* pdate, uint16_t* ptime);
+  /** \return All error bits. */
+  uint8_t getError() const {
+    return isOpen() ? m_error : 0XFF;
+  }
+  /** Get a file's modify date and time.
+   *
+   * \param[out] pdate Packed date for directory entry.
+   * \param[out] ptime Packed time for directory entry.
+   *
+   * \return true for success or false for failure.
+   */
+  bool getModifyDateTime(uint16_t* pdate, uint16_t* ptime);
+  /**
+   * Get a file's name followed by a zero.
+   *
+   * \param[out] name An array of characters for the file's name.
+   * \param[in] size The size of the array in characters.
+   * \return the name length.
+   */
+  size_t getName(char* name, size_t size) {
+#if USE_UTF8_LONG_NAMES
+    return getName8(name, size);
+#else  // USE_UTF8_LONG_NAMES
+    return getName7(name, size);
+#endif  // USE_UTF8_LONG_NAMES
+  }
+  /**
+   * Get a file's ASCII name followed by a zero.
+   *
+   * \param[out] name An array of characters for the file's name.
+   * \param[in] size The size of the array in characters.
+   * \return the name length.
+   */
+  size_t getName7(char* name, size_t size);
+  /**
+   * Get a file's UTF-8 name followed by a zero.
+   *
+   * \param[out] name An array of characters for the file's name.
+   * \param[in] size The size of the array in characters.
+   * \return the name length.
+   */
+  size_t getName8(char* name, size_t size);
+  /** \return value of writeError */
+  bool getWriteError() const {
+    return isOpen() ? m_error & WRITE_ERROR : true;
+  }
+  /**
+   * Check for FsBlockDevice busy.
+   *
+   * \return true if busy else false.
+   */
+  bool isBusy();
+  /** \return True if the file is contiguous. */
+  bool isContiguous() const {return m_flags & FILE_FLAG_CONTIGUOUS;}
+  /** \return True if this is a directory. */
+  bool isDir() const  {return m_attributes & FILE_ATTR_DIR;}
+  /** \return True if this is a normal file. */
+  bool isFile() const {return m_attributes & FILE_ATTR_FILE;}
+  /** \return True if this is a normal file or sub-directory. */
+  bool isFileOrSubDir() const {return isFile() || isSubDir();}
+  /** \return True if this is a hidden. */
+  bool isHidden() const {return m_attributes & FS_ATTRIB_HIDDEN;}
+  /** \return true if the file is open. */
+  bool isOpen() const {return m_attributes;}
+  /** \return True if file is read-only */
+  bool isReadOnly() const {return m_attributes & FS_ATTRIB_READ_ONLY;}
+  /** \return True if this is the root directory. */
+  bool isRoot() const {return m_attributes & FILE_ATTR_ROOT;}
+  /** \return True file is readable. */
+  bool isReadable() const {return m_flags & FILE_FLAG_READ;}
+  /** \return True if this is a sub-directory. */
+  bool isSubDir() const {return m_attributes & FILE_ATTR_SUBDIR;}
+  /** \return True if this is a system file. */
+  bool isSystem() const {return m_attributes & FS_ATTRIB_SYSTEM;}
+  /** \return True file is writable. */
+  bool isWritable() const {return m_flags & FILE_FLAG_WRITE;}
+  /** List directory contents.
+   *
+   * \param[in] pr Print stream for list.
+   * \return true for success or false for failure.
+   */
+  bool ls(print_t* pr);
+  /** List directory contents.
+   *
+   * \param[in] pr Print stream for list.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of sub-directories.
+   *
+   * \param[in] indent Amount of space before file name. Used for recursive
+   * list to indicate sub-directory level.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(print_t* pr, uint8_t flags, uint8_t indent = 0);
+  /** Make a new directory.
+   *
+   * \param[in] parent An open directory file that will
+   *                   contain the new directory.
+   *
+   * \param[in] path A path with a valid name for the new directory.
+   *
+   * \param[in] pFlag Create missing parent directories if true.
+   *
+   * \return true for success or false for failure.
+   */
+  bool mkdir(ExFatFile* parent, const char* path, bool pFlag = true);
+  /** Open a file or directory by name.
+   *
+   * \param[in] dirFile An open directory containing the file to be opened.
+   *
+   * \param[in] path The path for a file to be opened.
+   *
+   * \param[in] oflag Values for \a oflag are constructed by a
+   *                  bitwise-inclusive OR of flags from the following list.
+   *                  Only one of O_RDONLY, O_READ, O_WRONLY, O_WRITE, or
+   *                  O_RDWR is allowed.
+   *
+   * O_RDONLY - Open for reading.
+   *
+   * O_READ - Same as O_RDONLY.
+   *
+   * O_WRONLY - Open for writing.
+   *
+   * O_WRITE - Same as O_WRONLY.
+   *
+   * O_RDWR - Open for reading and writing.
+   *
+   * O_APPEND - If set, the file offset shall be set to the end of the
+   * file prior to each write.
+   *
+   * O_AT_END - Set the initial position at the end of the file.
+   *
+   * O_CREAT - If the file exists, this flag has no effect except as noted
+   * under O_EXCL below. Otherwise, the file shall be created
+   *
+   * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file
+   * exists.
+   *
+   * O_TRUNC - If the file exists and is a regular file, and the file is
+   * successfully opened and is not read only, its length shall be truncated
+   * to 0.
+   *
+   * WARNING: A given file must not be opened by more than one file object
+   * or file corruption may occur.
+   *
+   * \note Directory files must be opened read only.  Write and truncation is
+   * not allowed for directory files.
+   *
+   * \return true for success or false for failure.
+   */
+  bool open(ExFatFile* dirFile, const char* path, oflag_t oflag = O_RDONLY);
+  /** Open a file in the volume working directory.
+   *
+   * \param[in] vol Volume where the file is located.
+   *
+   * \param[in] path with a valid name for a file to be opened.
+   *
+   * \param[in] oflag bitwise-inclusive OR of open flags.
+   *                  See see open(ExFatFile*, const char*, uint8_t).
+   *
+   * \return true for success or false for failure.
+   */
+  bool open(ExFatVolume* vol, const char* path, oflag_t oflag = O_RDONLY);
+  /** Open a file by index.
+   *
+   * \param[in] dirFile An open ExFatFile instance for the directory.
+   *
+   * \param[in] index The \a index of the directory entry for the file to be
+   * opened.  The value for \a index is (directory file position)/32.
+   *
+   * \param[in] oflag bitwise-inclusive OR of open flags.
+   *            See see ExFatFile::open(ExFatFile*, const char*, uint8_t).
+   *
+   * See open() by path for definition of flags.
+   * \return true for success or false for failure.
+   */
+  bool open(ExFatFile* dirFile, uint32_t index, oflag_t oflag = O_RDONLY);
+  /** Open a file by index in the current working directory.
+   *
+   * \param[in] index The \a index of the directory entry for the file to be
+   * opened.  The value for \a index is (directory file position)/32.
+   *
+   * \param[in] oflag bitwise-inclusive OR of open flags.
+   *                  See see FatFile::open(FatFile*, const char*, uint8_t).
+   *
+   * See open() by path for definition of flags.
+   * \return true for success or false for failure.
+   */
+  bool open(uint32_t index, oflag_t oflag = O_RDONLY);
+  /** Open a file in the current working directory.
+   *
+   * \param[in] path A path with a valid name for a file to be opened.
+   *
+   * \param[in] oflag bitwise-inclusive OR of open flags.
+   *                  See see ExFatFile::open(ExFatFile*, const char*, uint8_t).
+   *
+   * \return true for success or false for failure.
+   */
+  bool open(const char* path, oflag_t oflag = O_RDONLY);
+   /** Open the current working directory.
+   *
+   * \return true for success or false for failure.
+   */
+  bool openCwd();
+  /** Open the next file or subdirectory in a directory.
+   *
+   * \param[in] dirFile An open instance for the directory
+   *                    containing the file to be opened.
+   *
+   * \param[in] oflag bitwise-inclusive OR of open flags.
+   *                  See see open(ExFatFile*, const char*, uint8_t).
+   *
+   * \return true for success or false for failure.
+   */
+  bool openNext(ExFatFile* dirFile, oflag_t oflag = O_RDONLY);
+  /** Open a volume's root directory.
+   *
+   * \param[in] vol The FAT volume containing the root directory to be opened.
+   *
+   * \return true for success or false for failure.
+   */
+  bool openRoot(ExFatVolume* vol);
+  /** Return the next available byte without consuming it.
+   *
+   * \return The byte if no error and not at eof else -1;
+   */
+  int peek();
+  /** Allocate contiguous clusters to an empty file.
+   *
+   * The file must be empty with no clusters allocated.
+   *
+   * The file will have zero validLength and dataLength
+   * will equal the requested length.
+   *
+   * \param[in] length size of allocated space in bytes.
+   * \return true for success or false for failure.
+   */
+  bool preAllocate(uint64_t length);
+     /** Print a file's access date and time
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return true for success or false for failure.
+   */
+  size_t printAccessDateTime(print_t* pr);
+   /** Print a file's creation date and time
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return true for success or false for failure.
+   */
+  size_t printCreateDateTime(print_t* pr);
+  /** Print a number followed by a field terminator.
+   * \param[in] value The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \param[in] prec Number of digits after decimal point.
+   * \return The number of bytes written or -1 if an error occurs.
+   */
+  size_t printField(double value, char term, uint8_t prec = 2) {
+    char buf[24];
+    char* str = buf + sizeof(buf);
+    if (term) {
+      *--str = term;
+      if (term == '\n') {
+        *--str = '\r';
+      }
+    }
+    str = fmtDouble(str, value, prec, false);
+    return write(str, buf + sizeof(buf) - str);
+  }
+  /** Print a number followed by a field terminator.
+   * \param[in] value The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \param[in] prec Number of digits after decimal point.
+   * \return The number of bytes written or -1 if an error occurs.
+   */
+  size_t printField(float value, char term, uint8_t prec = 2) {
+    return printField(static_cast<double>(value), term, prec);
+  }
+  /** Print a number followed by a field terminator.
+   * \param[in] value The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \return The number of bytes written or -1 if an error occurs.
+   */
+  template <typename Type>
+  size_t printField(Type value, char term) {
+    char sign = 0;
+    char buf[3*sizeof(Type) + 3];
+    char* str = buf + sizeof(buf);
+
+    if (term) {
+      *--str = term;
+      if (term == '\n') {
+        *--str = '\r';
+      }
+    }
+    if (value < 0) {
+      value = -value;
+      sign = '-';
+    }
+    if (sizeof(Type) < 4) {
+      str = fmtBase10(str, (uint16_t)value);
+    } else {
+      str = fmtBase10(str, (uint32_t)value);
+    }
+    if (sign) {
+      *--str = sign;
+    }
+    return write(str, &buf[sizeof(buf)] - str);
+  }
+  /** Print a file's size in bytes.
+   * \param[in] pr Prtin stream for the output.
+   * \return The number of bytes printed.
+   */
+  size_t printFileSize(print_t* pr);
+  /** Print a file's modify date and time
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return true for success or false for failure.
+   */
+  size_t printModifyDateTime(print_t* pr);
+  /** Print a file's name
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return length for success or zero for failure.
+   */
+  size_t printName(print_t* pr) {
+#if USE_UTF8_LONG_NAMES
+    return printName8(pr);
+#else  // USE_UTF8_LONG_NAMES
+    return printName7(pr);
+#endif  // USE_UTF8_LONG_NAMES
+  }
+  /** Print a file's ASCII name
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return true for success or false for failure.
+   */
+  size_t printName7(print_t* pr);
+  /** Print a file's UTF-8 name
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return true for success or false for failure.
+   */
+  size_t printName8(print_t* pr);
+  /** Read the next byte from a file.
+   *
+   * \return For success read returns the next byte in the file as an int.
+   * If an error occurs or end of file is reached -1 is returned.
+   */
+  int read() {
+    uint8_t b;
+    return read(&b, 1) == 1 ? b : -1;
+  }
+  /** Read data from a file starting at the current position.
+   *
+   * \param[out] buf Pointer to the location that will receive the data.
+   *
+   * \param[in] count Maximum number of bytes to read.
+   *
+   * \return For success read() returns the number of bytes read.
+   * A value less than \a nbyte, including zero, will be returned
+   * if end of file is reached.
+   * If an error occurs, read() returns -1.
+   */
+  int read(void* buf, size_t count);
+  /** Remove a file.
+   *
+   * The directory entry and all data for the file are deleted.
+   *
+   * \note This function should not be used to delete the 8.3 version of a
+   * file that has a long name. For example if a file has the long name
+   * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
+   *
+   * \return true for success or false for failure.
+   */
+  bool remove();
+  /** Remove a file.
+   *
+   * The directory entry and all data for the file are deleted.
+   *
+   * \param[in] path Path for the file to be removed.
+   *
+   * Example use: dirFile.remove(filenameToRemove);
+   *
+   * \note This function should not be used to delete the 8.3 version of a
+   * file that has a long name. For example if a file has the long name
+   * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
+   *
+   * \return true for success or false for failure.
+   */
+  bool remove(const char* path);
+   /** Rename a file or subdirectory.
+   *
+   * \param[in] newPath New path name for the file/directory.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rename(const char* newPath);
+   /** Rename a file or subdirectory.
+   *
+   * \param[in] dirFile Directory for the new path.
+   * \param[in] newPath New path name for the file/directory.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rename(ExFatFile* dirFile, const char* newPath);
+  /** Set the file's current position to zero. */
+  void rewind() {
+    seekSet(0);
+  }
+  /** Remove a directory file.
+   *
+   * The directory file will be removed only if it is empty and is not the
+   * root directory.  rmdir() follows DOS and Windows and ignores the
+   * read-only attribute for the directory.
+   *
+   * \note This function should not be used to delete the 8.3 version of a
+   * directory that has a long name. For example if a directory has the
+   * long name "New folder" you should not delete the 8.3 name "NEWFOL~1".
+   *
+   * \return true for success or false for failure.
+   */
+  bool rmdir();
+  /** Set the files position to current position + \a pos. See seekSet().
+   * \param[in] offset The new position in bytes from the current position.
+   * \return true for success or false for failure.
+   */
+  bool seekCur(int64_t offset) {
+    return seekSet(m_curPosition + offset);
+  }
+  /** Set the files position to end-of-file + \a offset. See seekSet().
+   * Can't be used for directory files since file size is not defined.
+   * \param[in] offset The new position in bytes from end-of-file.
+   * \return true for success or false for failure.
+   */
+  bool seekEnd(int64_t offset = 0) {
+    return isFile() ? seekSet(m_validLength + offset) : false;
+  }
+  /** Sets a file's position.
+   *
+   * \param[in] pos The new position in bytes from the beginning of the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool seekSet(uint64_t pos);
+  /** \return directory set count */
+  uint8_t setCount() const {return m_setCount;}
+  /** The sync() call causes all modified data and directory fields
+   * to be written to the storage device.
+   *
+   * \return true for success or false for failure.
+   */
+  bool sync();
+  /** Truncate a file at the current file position.
+   *
+   * \return true for success or false for failure.
+   */
+  /** Set a file's timestamps in its directory entry.
+   *
+   * \param[in] flags Values for \a flags are constructed by a
+   * bitwise-inclusive OR of flags from the following list
+   *
+   * T_ACCESS - Set the file's last access date and time.
+   *
+   * T_CREATE - Set the file's creation date and time.
+   *
+   * T_WRITE - Set the file's last write/modification date and time.
+   *
+   * \param[in] year Valid range 1980 - 2107 inclusive.
+   *
+   * \param[in] month Valid range 1 - 12 inclusive.
+   *
+   * \param[in] day Valid range 1 - 31 inclusive.
+   *
+   * \param[in] hour Valid range 0 - 23 inclusive.
+   *
+   * \param[in] minute Valid range 0 - 59 inclusive.
+   *
+   * \param[in] second Valid range 0 - 59 inclusive
+   *
+   * \note It is possible to set an invalid date since there is no check for
+   * the number of days in a month.
+   *
+   * \note
+   * Modify and access timestamps may be overwritten if a date time callback
+   * function has been set by dateTimeCallback().
+   *
+   * \return true for success or false for failure.
+   */
+  bool timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day,
+                 uint8_t hour, uint8_t minute, uint8_t second);
+  /** Truncate a file at the current file position.
+   * will be maintained if it is less than or equal to \a length otherwise
+   * it will be set to end of file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool truncate();
+   /** Truncate a file to a specified length.  The current file position
+   * will be set to end of file.
+   *
+   * \param[in] length The desired length for the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool truncate(uint64_t length) {
+    return seekSet(length) && truncate();
+  }
+
+  /** \return The valid number of bytes in a file. */
+  uint64_t validLength() const {return m_validLength;}
+  /** Write a string to a file. Used by the Arduino Print class.
+   * \param[in] str Pointer to the string.
+   * Use getWriteError to check for errors.
+   * \return count of characters written for success or -1 for failure.
+   */
+  size_t write(const char* str) {
+    return write(str, strlen(str));
+  }
+  /** Write a single byte.
+   * \param[in] b The byte to be written.
+   * \return +1 for success or zero for failure.
+   */
+  size_t write(uint8_t b) {return write(&b, 1);}
+  /** Write data to an open file.
+   *
+   * \note Data is moved to the cache but may not be written to the
+   * storage device until sync() is called.
+   *
+   * \param[in] buf Pointer to the location of the data to be written.
+   *
+   * \param[in] count Number of bytes to write.
+   *
+   * \return For success write() returns the number of bytes written, always
+   * \a count. If an error occurs, write() returns zero and writeError is set.
+   */
+  size_t write(const void* buf, size_t count);
+//------------------------------------------------------------------------------
+#if ENABLE_ARDUINO_SERIAL
+  /** List directory contents.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(uint8_t flags = 0) {
+    return ls(&Serial, flags);
+  }
+  /** Print a file's name.
+   *
+   * \return length for success or zero for failure.
+   */
+  size_t printName() {
+    return ExFatFile::printName(&Serial);
+  }
+#endif  // ENABLE_ARDUINO_SERIAL
+
+ private:
+  /** ExFatVolume allowed access to private members. */
+  friend class ExFatVolume;
+  bool addCluster();
+  bool addDirCluster();
+  bool cmpName(const DirName_t* dirName, ExName_t* fname);
+  uint8_t* dirCache(uint8_t set, uint8_t options);
+  bool hashName(ExName_t* fname);
+  bool mkdir(ExFatFile* parent, ExName_t* fname);
+
+  bool openPrivate(ExFatFile* dir, ExName_t* fname, oflag_t oflag);
+  bool parsePathName(const char* path,
+                            ExName_t* fname, const char** ptr);
+  ExFatVolume* volume() const {return m_vol;}
+  bool syncDir();
+  //----------------------------------------------------------------------------
+  static const uint8_t WRITE_ERROR = 0X1;
+  static const uint8_t READ_ERROR  = 0X2;
+
+  /** This file has not been opened. */
+  static const uint8_t FILE_ATTR_CLOSED = 0;
+  /** Entry for normal data file */
+  static const uint8_t FILE_ATTR_FILE = 0X08;
+  /** Entry is for a subdirectory */
+  static const uint8_t FILE_ATTR_SUBDIR = FS_ATTRIB_DIRECTORY;
+  /** Root directory */
+  static const uint8_t FILE_ATTR_ROOT = 0X40;
+  /** Directory type bits */
+  static const uint8_t FILE_ATTR_DIR = FILE_ATTR_SUBDIR | FILE_ATTR_ROOT;
+
+  static const uint8_t FILE_FLAG_READ = 0X01;
+  static const uint8_t FILE_FLAG_WRITE = 0X02;
+  static const uint8_t FILE_FLAG_APPEND = 0X08;
+  static const uint8_t FILE_FLAG_CONTIGUOUS  = 0X40;
+  static const uint8_t FILE_FLAG_DIR_DIRTY = 0X80;
+
+
+  uint64_t      m_curPosition;
+  uint64_t      m_dataLength;
+  uint64_t      m_validLength;
+  uint32_t      m_curCluster;
+  uint32_t      m_firstCluster;
+  ExFatVolume*  m_vol;
+  DirPos_t      m_dirPos;
+  uint8_t       m_setCount;
+  uint8_t       m_attributes = FILE_ATTR_CLOSED;
+  uint8_t       m_error = 0;
+  uint8_t       m_flags = 0;
+};
+
+#include "../common/ArduinoFiles.h"
+/**
+ * \class ExFile
+ * \brief exFAT file with Arduino Stream.
+ */
+class ExFile : public StreamFile<ExFatFile, uint64_t> {
+ public:
+   /** Opens the next file or folder in a directory.
+   *
+   * \param[in] oflag open flags.
+   * \return a FatStream object.
+   */
+  ExFile openNextFile(oflag_t oflag = O_RDONLY) {
+    ExFile tmpFile;
+    tmpFile.openNext(this, oflag);
+    return tmpFile;
+  }
+};
+#endif  // ExFatFile_h

+ 228 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatFilePrint.cpp

@@ -0,0 +1,228 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "ExFatFilePrint.cpp"
+#include "../common/DebugMacros.h"
+#include "ExFatLib.h"
+#include "../common/FsUtf.h"
+//------------------------------------------------------------------------------
+bool ExFatFile::ls(print_t* pr) {
+  ExFatFile file;
+  if (!isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  rewind();
+  while (file.openNext(this, O_RDONLY)) {
+    if (!file.isHidden()) {
+      file.printName(pr);
+      if (file.isDir()) {
+        pr->write('/');
+      }
+      pr->write('\r');
+      pr->write('\n');
+    }
+    file.close();
+  }
+  if (getError()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::ls(print_t* pr, uint8_t flags, uint8_t indent) {
+  ExFatFile file;
+  if (!isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  rewind();
+  while (file.openNext(this, O_RDONLY)) {
+    // indent for dir level
+    if (!file.isHidden() || (flags & LS_A)) {
+      for (uint8_t i = 0; i < indent; i++) {
+        pr->write(' ');
+      }
+      if (flags & LS_DATE) {
+        file.printModifyDateTime(pr);
+        pr->write(' ');
+      }
+      if (flags & LS_SIZE) {
+        file.printFileSize(pr);
+        pr->write(' ');
+      }
+      file.printName(pr);
+      if (file.isDir()) {
+        pr->write('/');
+      }
+      pr->write('\r');
+      pr->write('\n');
+      if ((flags & LS_R) && file.isDir()) {
+        file.ls(pr, flags, indent + 2);
+      }
+    }
+    file.close();
+  }
+  if (getError()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+size_t ExFatFile::printAccessDateTime(print_t* pr) {
+  uint16_t date;
+  uint16_t time;
+  if (getAccessDateTime(&date, &time)) {
+    return fsPrintDateTime(pr, date, time);
+  }
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t ExFatFile::printCreateDateTime(print_t* pr) {
+  uint16_t date;
+  uint16_t time;
+  if (getCreateDateTime(&date, &time)) {
+    return fsPrintDateTime(pr, date, time);
+  }
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t ExFatFile::printFileSize(print_t* pr) {
+  uint64_t n = m_validLength;
+  char buf[21];
+  char *str = &buf[sizeof(buf) - 1];
+  char *bgn = str - 12;
+  *str = '\0';
+  do {
+    uint64_t m = n;
+    n /= 10;
+    *--str = m - 10*n + '0';
+  } while (n);
+  while (str > bgn) {
+    *--str = ' ';
+  }
+  return pr->write(str);
+}
+//------------------------------------------------------------------------------
+size_t ExFatFile::printModifyDateTime(print_t* pr) {
+  uint16_t date;
+  uint16_t time;
+  if (getModifyDateTime(&date, &time)) {
+    return fsPrintDateTime(pr, date, time);
+  }
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t ExFatFile::printName7(print_t* pr) {
+  DirName_t* dn;
+  size_t n = 0;
+  uint8_t in;
+  uint8_t buf[15];
+  if (!isOpen()) {
+      DBG_FAIL_MACRO;
+      goto fail;
+  }
+  for (uint8_t is = 2; is <= m_setCount; is++) {
+    dn = reinterpret_cast<DirName_t*>
+         (dirCache(is, FsCache::CACHE_FOR_READ));
+    if (!dn || dn->type != EXFAT_TYPE_NAME) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    for (in = 0; in < 15; in++) {
+      uint16_t c = getLe16(dn->unicode + 2*in);
+      if (!c) {
+        break;
+      }
+      buf[in] = c < 0X7F ? c : '?';
+      n++;
+    }
+    pr->write(buf, in);
+  }
+  return n;
+
+ fail:
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t ExFatFile::printName8(print_t *pr) {
+  DirName_t* dn;
+  uint16_t hs = 0;
+  uint32_t cp;
+  size_t n = 0;
+  uint8_t in;
+  char buf[5];
+  if (!isOpen()) {
+      DBG_FAIL_MACRO;
+      goto fail;
+  }
+  for (uint8_t is = 2; is <= m_setCount; is++) {
+    dn = reinterpret_cast<DirName_t*>
+         (dirCache(is, FsCache::CACHE_FOR_READ));
+    if (!dn || dn->type != EXFAT_TYPE_NAME) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    for (in = 0; in < 15; in++) {
+      uint16_t c = getLe16(dn->unicode + 2*in);
+      if (hs) {
+        if (!FsUtf::isLowSurrogate(c)) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+        cp = FsUtf::u16ToCp(hs, c);
+        hs = 0;
+      } else if (!FsUtf::isSurrogate(c)) {
+        if (c == 0) {
+          break;
+        }
+        cp = c;
+      } else if (FsUtf::isHighSurrogate(c)) {
+        hs = c;
+        continue;
+      } else {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      char* str = FsUtf::cpToMb(cp, buf, buf + sizeof(buf));
+      if (!str) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      n += pr->write(buf, str - buf);
+    }
+  }
+  return n;
+
+ fail:
+  return 0;
+}

+ 756 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatFileWrite.cpp

@@ -0,0 +1,756 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "ExFatFileWrite.cpp"
+#include "../common/DebugMacros.h"
+#include "ExFatLib.h"
+//==============================================================================
+#if EXFAT_READ_ONLY
+bool ExFatFile::mkdir(ExFatFile* parent, const char* path, bool pFlag) {
+  (void) parent;
+  (void)path;
+  (void)pFlag;
+  return false;
+}
+bool ExFatFile::preAllocate(uint64_t length) {
+  (void)length;
+  return false;
+}
+bool ExFatFile::rename(const char* newPath) {
+  (void)newPath;
+  return false;
+}
+bool ExFatFile::rename(ExFatFile* dirFile, const char* newPath) {
+  (void)dirFile;
+  (void)newPath;
+  return false;
+}
+bool ExFatFile::sync() {
+  return false;
+}
+bool ExFatFile::truncate() {
+  return false;
+}
+size_t ExFatFile::write(const void* buf, size_t nbyte) {
+  (void)buf;
+  (void)nbyte;
+  return false;
+}
+//==============================================================================
+#else  // EXFAT_READ_ONLY
+//------------------------------------------------------------------------------
+static uint16_t exFatDirChecksum(const uint8_t* data, uint16_t checksum) {
+  bool skip = data[0] == EXFAT_TYPE_FILE;
+  for (size_t i = 0; i < 32; i += i == 1 && skip ? 3 : 1) {
+    checksum = ((checksum << 15) | (checksum >> 1)) + data[i];
+  }
+  return checksum;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::addCluster() {
+  uint32_t find = m_vol->bitmapFind(m_curCluster ?  m_curCluster + 1 : 0, 1);
+  if (find < 2) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (!m_vol->bitmapModify(find, 1, 1)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (m_curCluster == 0) {
+    m_flags |= FILE_FLAG_CONTIGUOUS;
+    goto done;
+  }
+  if (isContiguous()) {
+    if (find == (m_curCluster + 1)) {
+      goto done;
+    }
+    // No longer contiguous so make FAT chain.
+    m_flags &= ~FILE_FLAG_CONTIGUOUS;
+
+    for (uint32_t c = m_firstCluster; c < m_curCluster; c++) {
+      if (!m_vol->fatPut(c, c + 1)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+  }
+  // New cluster is EOC.
+  if (!m_vol->fatPut(find, EXFAT_EOC)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Connect new cluster to existing chain.
+  if (m_curCluster) {
+    if (!m_vol->fatPut(m_curCluster, find)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+
+ done:
+  m_curCluster = find;
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::addDirCluster() {
+  uint32_t sector;
+  uint32_t dl = isRoot() ? m_vol->rootLength() : m_dataLength;
+  uint8_t* cache;
+  dl += m_vol->bytesPerCluster();
+  if (dl >= 0X4000000) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (!addCluster()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  sector = m_vol->clusterStartSector(m_curCluster);
+  for (uint32_t i = 0; i  < m_vol->sectorsPerCluster(); i++) {
+    cache = m_vol->dataCachePrepare(sector + i,
+                                    FsCache::CACHE_RESERVE_FOR_WRITE);
+    if (!cache) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    memset(cache, 0, m_vol->bytesPerSector());
+  }
+  if (!isRoot()) {
+    m_flags |= FILE_FLAG_DIR_DIRTY;
+    m_dataLength  += m_vol->bytesPerCluster();
+    m_validLength += m_vol->bytesPerCluster();
+  }
+  return sync();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::mkdir(ExFatFile* parent, const char* path, bool pFlag) {
+  ExName_t fname;
+  ExFatFile tmpDir;
+
+  if (isOpen() || !parent->isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (isDirSeparator(*path)) {
+    while (isDirSeparator(*path)) {
+      path++;
+    }
+    if (!tmpDir.openRoot(parent->m_vol)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    parent = &tmpDir;
+  }
+  while (1) {
+    if (!parsePathName(path, &fname, &path)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (!*path) {
+      break;
+    }
+    if (!openPrivate(parent, &fname, O_RDONLY)) {
+      if (!pFlag || !mkdir(parent, &fname)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+    tmpDir = *this;
+    parent = &tmpDir;
+    close();
+  }
+  return mkdir(parent, &fname);
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::mkdir(ExFatFile* parent, ExName_t* fname) {
+  if (!parent->isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // create a normal file
+  if (!openPrivate(parent, fname, O_CREAT | O_EXCL | O_RDWR)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // convert file to directory
+  m_attributes = FILE_ATTR_SUBDIR | FS_ATTRIB_ARCHIVE;
+
+  // allocate and zero first cluster
+  if (!addDirCluster()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  m_firstCluster = m_curCluster;
+
+  // Set to start of dir
+  rewind();
+  m_flags = FILE_FLAG_READ | FILE_FLAG_CONTIGUOUS | FILE_FLAG_DIR_DIRTY;
+  return sync();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::preAllocate(uint64_t length) {
+  uint32_t find;
+  uint32_t need;
+  if (!length || !isWritable() || m_firstCluster) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  need = 1 + ((length - 1) >> m_vol->bytesPerClusterShift());
+  find = m_vol->bitmapFind(0, need);
+  if (find < 2) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (!m_vol->bitmapModify(find, need, 1)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  m_dataLength = length;
+  m_firstCluster = find;
+  m_flags |= FILE_FLAG_DIR_DIRTY | FILE_FLAG_CONTIGUOUS;
+  if (!sync()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::remove() {
+  uint8_t* cache;
+  if (!isWritable()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Free any clusters.
+  if (m_firstCluster) {
+    if (isContiguous()) {
+      uint32_t nc = 1 + ((m_dataLength - 1) >> m_vol->bytesPerClusterShift());
+      if (!m_vol->bitmapModify(m_firstCluster, nc, 0)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    } else {
+      if (!m_vol->freeChain(m_firstCluster)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+  }
+
+  for (uint8_t is = 0; is <= m_setCount; is++) {
+    cache = dirCache(is, FsCache::CACHE_FOR_WRITE);
+    if (!cache) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    // Mark entry not used.
+    cache[0] &= 0x7F;
+  }
+  // Set this file closed.
+  m_attributes = FILE_ATTR_CLOSED;
+  m_flags = 0;
+
+  // Write entry to device.
+  return m_vol->cacheSync();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::rename(const char* newPath) {
+  return rename(m_vol->vwd(), newPath);
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::rename(ExFatFile* dirFile, const char* newPath) {
+  ExFatFile file;
+  ExFatFile oldFile;
+
+  // Must be an open file or subdirectory.
+  if (!(isFile() || isSubDir())) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Can't move file to new volume.
+  if (m_vol != dirFile->m_vol) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (!file.open(dirFile, newPath, O_CREAT | O_EXCL | O_WRONLY)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  oldFile = *this;
+  m_dirPos = file.m_dirPos;
+  m_setCount = file.m_setCount;
+  m_flags |= FILE_FLAG_DIR_DIRTY;
+  if (!sync()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Remove old directory entry;
+  oldFile.m_firstCluster = 0;
+  oldFile.m_flags = FILE_FLAG_WRITE;
+  oldFile.m_attributes = FILE_ATTR_FILE;
+  return oldFile.remove();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::rmdir() {
+  int n;
+  uint8_t dir[FS_DIR_SIZE];
+  // must be open subdirectory
+  if (!isSubDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  rewind();
+
+  // make sure directory is empty
+  while (1) {
+    n = read(dir, FS_DIR_SIZE);
+    if (n == 0) {
+      break;
+    }
+    if (n != FS_DIR_SIZE || dir[0] & 0X80) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (dir[0] == 0) {
+      break;
+    }
+  }
+  // convert empty directory to normal file for remove
+  m_attributes = FILE_ATTR_FILE;
+  m_flags |= FILE_FLAG_WRITE;
+  return remove();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::sync() {
+  if (!isOpen()) {
+    return true;
+  }
+  if (m_flags & FILE_FLAG_DIR_DIRTY) {
+    // clear directory dirty
+    m_flags &= ~FILE_FLAG_DIR_DIRTY;
+    return syncDir();
+  }
+  if (!m_vol->cacheSync()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  m_error |= WRITE_ERROR;
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::syncDir() {
+  DirFile_t* df;
+  DirStream_t* ds;
+  uint8_t* cache;
+  uint16_t checksum = 0;
+
+  for (uint8_t is = 0; is <= m_setCount ; is++) {
+    cache = dirCache(is, FsCache::CACHE_FOR_READ);
+    if (!cache) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    switch (cache[0]) {
+      case EXFAT_TYPE_FILE:
+        df = reinterpret_cast<DirFile_t*>(cache);
+        setLe16(df->attributes, m_attributes & FS_ATTRIB_COPY);
+        if (FsDateTime::callback) {
+          uint16_t date, time;
+          uint8_t ms10;
+          FsDateTime::callback(&date, &time, &ms10);
+          df->modifyTimeMs = ms10;
+          setLe16(df->modifyTime, time);
+          setLe16(df->modifyDate, date);
+          setLe16(df->accessTime, time);
+          setLe16(df->accessDate, date);
+        }
+        m_vol->dataCacheDirty();
+        break;
+
+      case EXFAT_TYPE_STREAM:
+        ds = reinterpret_cast<DirStream_t*>(cache);
+        if (isContiguous()) {
+          ds->flags |= EXFAT_FLAG_CONTIGUOUS;
+        } else {
+          ds->flags &= ~EXFAT_FLAG_CONTIGUOUS;
+        }
+        setLe64(ds->validLength, m_validLength);
+        setLe32(ds->firstCluster, m_firstCluster);
+        setLe64(ds->dataLength, m_dataLength);
+        m_vol->dataCacheDirty();
+        break;
+
+      case EXFAT_TYPE_NAME:
+        break;
+
+      default:
+        DBG_FAIL_MACRO;
+        goto fail;
+        break;
+    }
+    checksum = exFatDirChecksum(cache, checksum);
+  }
+  df = reinterpret_cast<DirFile_t*>
+       (m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_WRITE));
+  if (!df) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  setLe16(df->setChecksum, checksum);
+  if (!m_vol->cacheSync()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  m_error |= WRITE_ERROR;
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::timestamp(uint8_t flags, uint16_t year, uint8_t month,
+                   uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
+  DirFile_t* df;
+  uint8_t* cache;
+  uint16_t checksum = 0;
+  uint16_t date;
+  uint16_t time;
+  uint8_t ms10;
+
+  if (!isFile()
+      || year < 1980
+      || year > 2107
+      || month < 1
+      || month > 12
+      || day < 1
+      || day > 31
+      || hour > 23
+      || minute > 59
+      || second > 59) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // update directory entry
+  if (!sync()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+
+  date = FS_DATE(year, month, day);
+  time = FS_TIME(hour, minute, second);
+  ms10 = second & 1 ? 100 : 0;
+
+  for (uint8_t is = 0; is <= m_setCount; is++) {
+    cache = dirCache(is, FsCache::CACHE_FOR_READ);
+    if (!cache) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    switch (cache[0]) {
+      case EXFAT_TYPE_FILE:
+        df = reinterpret_cast<DirFile_t*>(cache);
+        setLe16(df->attributes, m_attributes & FS_ATTRIB_COPY);
+        m_vol->dataCacheDirty();
+        if (flags & T_ACCESS) {
+          setLe16(df->accessTime, time);
+          setLe16(df->accessDate, date);
+        }
+        if (flags & T_CREATE) {
+          df->createTimeMs = ms10;
+          setLe16(df->createTime, time);
+          setLe16(df->createDate, date);
+        }
+        if (flags & T_WRITE) {
+          df->modifyTimeMs = ms10;
+          setLe16(df->modifyTime, time);
+          setLe16(df->modifyDate, date);
+        }
+        break;
+
+      case EXFAT_TYPE_STREAM:
+        break;
+
+      case EXFAT_TYPE_NAME:
+        break;
+
+      default:
+        DBG_FAIL_MACRO;
+        goto fail;
+        break;
+    }
+    checksum = exFatDirChecksum(cache, checksum);
+  }
+  df = reinterpret_cast<DirFile_t*>
+       (m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_WRITE));
+  if (!df) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  setLe16(df->setChecksum, checksum);
+  if (!m_vol->cacheSync()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::truncate() {
+  uint32_t toFree;
+  // error if not a normal file or read-only
+  if (!isWritable()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (m_firstCluster == 0) {
+      return true;
+  }
+  if (isContiguous()) {
+    uint32_t nc = 1 + ((m_dataLength - 1) >> m_vol->bytesPerClusterShift());
+    if (m_curCluster) {
+      toFree = m_curCluster + 1;
+      nc -= 1 + m_curCluster - m_firstCluster;
+    } else {
+      toFree = m_firstCluster;
+      m_firstCluster = 0;
+    }
+    if (nc && !m_vol->bitmapModify(toFree, nc, 0)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  } else {
+    // need to free chain
+    if (m_curCluster) {
+      toFree = 0;
+      int8_t fg = m_vol->fatGet(m_curCluster, &toFree);
+      if (fg < 0) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      if (fg) {
+        // current cluster is end of chain
+        if (!m_vol->fatPut(m_curCluster, EXFAT_EOC)) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+      }
+    } else {
+      toFree = m_firstCluster;
+      m_firstCluster = 0;
+    }
+    if (toFree) {
+      if (!m_vol->freeChain(toFree)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+  }
+  m_dataLength = m_curPosition;
+  m_validLength = m_curPosition;
+  m_flags |= FILE_FLAG_DIR_DIRTY;
+  return sync();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+size_t ExFatFile::write(const void* buf, size_t nbyte) {
+  // convert void* to uint8_t*  -  must be before goto statements
+  const uint8_t* src = reinterpret_cast<const uint8_t*>(buf);
+  uint8_t* cache;
+  uint8_t cacheOption;
+  uint16_t sectorOffset;
+  uint32_t sector;
+  uint32_t clusterOffset;
+
+  // number of bytes left to write  -  must be before goto statements
+  size_t toWrite = nbyte;
+  size_t n;
+  // error if not an open file or is read-only
+  if (!isWritable()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // seek to end of file if append flag
+  if ((m_flags & FILE_FLAG_APPEND)) {
+    if (!seekSet(m_validLength)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+  while (toWrite) {
+    clusterOffset = m_curPosition & m_vol->clusterMask();
+    sectorOffset = clusterOffset & m_vol->sectorMask();
+    if (clusterOffset == 0) {
+      // start of new cluster
+      if (m_curCluster != 0) {
+        int fg;
+
+        if (isContiguous()) {
+          uint32_t lc = m_firstCluster;
+          lc += (m_dataLength - 1) >> m_vol->bytesPerClusterShift();
+          if (m_curCluster < lc) {
+            m_curCluster++;
+            fg = 1;
+          } else {
+            fg = 0;
+          }
+        } else {
+          fg = m_vol->fatGet(m_curCluster, &m_curCluster);
+          if (fg < 0) {
+            DBG_FAIL_MACRO;
+            goto fail;
+          }
+        }
+        if (fg == 0) {
+          // add cluster if at end of chain
+          if (!addCluster()) {
+            DBG_FAIL_MACRO;
+            goto fail;
+          }
+        }
+      } else {
+        if (m_firstCluster == 0) {
+          // allocate first cluster of file
+          if (!addCluster()) {
+            DBG_FAIL_MACRO;
+            goto fail;
+          }
+          m_firstCluster = m_curCluster;
+        } else {
+          m_curCluster = m_firstCluster;
+        }
+      }
+    }
+    // sector for data write
+    sector = m_vol->clusterStartSector(m_curCluster) +
+             (clusterOffset >> m_vol->bytesPerSectorShift());
+
+    if (sectorOffset != 0 || toWrite < m_vol->bytesPerSector()) {
+      // partial sector - must use cache
+      // max space in sector
+      n = m_vol->bytesPerSector() - sectorOffset;
+      // lesser of space and amount to write
+      if (n > toWrite) {
+        n = toWrite;
+      }
+
+      if (sectorOffset == 0 && m_curPosition >= m_validLength) {
+        // start of new sector don't need to read into cache
+        cacheOption = FsCache::CACHE_RESERVE_FOR_WRITE;
+      } else {
+        // rewrite part of sector
+        cacheOption = FsCache::CACHE_FOR_WRITE;
+      }
+      cache = m_vol->dataCachePrepare(sector, cacheOption);
+      if (!cache) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      uint8_t* dst = cache + sectorOffset;
+      memcpy(dst, src, n);
+      if (m_vol->bytesPerSector() == (n + sectorOffset)) {
+        // Force write if sector is full - improves large writes.
+        if (!m_vol->dataCacheSync()) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+      }
+#if USE_MULTI_SECTOR_IO
+    } else if (toWrite >= 2*m_vol->bytesPerSector()) {
+      // use multiple sector write command
+      uint32_t ns = toWrite >> m_vol->bytesPerSectorShift();
+      // Limit writes to current cluster.
+      uint32_t maxNs = m_vol->sectorsPerCluster()
+                       - (clusterOffset >> m_vol->bytesPerSectorShift());
+      if (ns > maxNs) {
+        ns = maxNs;
+      }
+      n = ns << m_vol->bytesPerSectorShift();
+      if (!m_vol->cacheSafeWrite(sector, src, ns)) {
+         DBG_FAIL_MACRO;
+        goto fail;
+      }
+#endif  // USE_MULTI_SECTOR_IO
+    } else {
+      n = m_vol->bytesPerSector();
+      if (!m_vol->cacheSafeWrite(sector, src)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+    m_curPosition += n;
+    src += n;
+    toWrite -= n;
+    if (m_curPosition > m_validLength) {
+      m_flags |= FILE_FLAG_DIR_DIRTY;
+      m_validLength = m_curPosition;
+    }
+  }
+  if (m_curPosition > m_dataLength) {
+    m_dataLength = m_curPosition;
+    // update fileSize and insure sync will update dir entry
+    m_flags |= FILE_FLAG_DIR_DIRTY;
+  } else if (FsDateTime::callback) {
+    // insure sync will update modified date and time
+    m_flags |= FILE_FLAG_DIR_DIRTY;
+  }
+  return nbyte;
+
+ fail:
+  // return for write error
+  m_error |= WRITE_ERROR;
+  return 0;
+}
+#endif  // EXFAT_READ_ONLY

+ 363 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatFormatter.cpp

@@ -0,0 +1,363 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "ExFatFormatter.cpp"
+#include "../common/DebugMacros.h"
+#include "../common/upcase.h"
+#include "ExFatLib.h"
+//------------------------------------------------------------------------------
+// Formatter assumes 512 byte sectors.
+const uint32_t BOOT_BACKUP_OFFSET = 12;
+const uint16_t BYTES_PER_SECTOR = 512;
+const uint16_t SECTOR_MASK = BYTES_PER_SECTOR - 1;
+const uint8_t  BYTES_PER_SECTOR_SHIFT = 9;
+const uint16_t MINIMUM_UPCASE_SKIP = 512;
+const uint32_t BITMAP_CLUSTER = 2;
+const uint32_t UPCASE_CLUSTER = 3;
+const uint32_t ROOT_CLUSTER = 4;
+//------------------------------------------------------------------------------
+#define PRINT_FORMAT_PROGRESS 1
+#if !PRINT_FORMAT_PROGRESS
+#define writeMsg(pr, str)
+#elif defined(__AVR__)
+#define writeMsg(pr, str) if (pr) pr->print(F(str))
+#else  // PRINT_FORMAT_PROGRESS
+#define writeMsg(pr, str) if (pr) pr->write(str)
+#endif  // PRINT_FORMAT_PROGRESS
+//------------------------------------------------------------------------------
+bool ExFatFormatter::format(FsBlockDevice* dev, uint8_t* secBuf, print_t* pr) {
+#if !PRINT_FORMAT_PROGRESS
+(void)pr;
+#endif  //  !PRINT_FORMAT_PROGRESS
+  MbrSector_t* mbr;
+  ExFatPbs_t* pbs;
+  DirUpcase_t* dup;
+  DirBitmap_t* dbm;
+  DirLabel_t* label;
+  uint32_t bitmapSize;
+  uint32_t checksum = 0;
+  uint32_t clusterCount;
+  uint32_t clusterHeapOffset;
+  uint32_t fatLength;
+  uint32_t fatOffset;
+  uint32_t m;
+  uint32_t ns;
+  uint32_t partitionOffset;
+  uint32_t sector;
+  uint32_t sectorsPerCluster;
+  uint32_t volumeLength;
+  uint32_t sectorCount;
+  uint8_t sectorsPerClusterShift;
+  uint8_t vs;
+
+  m_dev = dev;
+  m_secBuf = secBuf;
+  sectorCount = dev->sectorCount();
+  // Min size is 512 MB
+  if (sectorCount < 0X100000) {
+    writeMsg(pr, "Device is too small\r\n");
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Determine partition layout.
+  for (m = 1, vs = 0; m && sectorCount > m; m <<= 1, vs++) {}
+  sectorsPerClusterShift = vs < 29 ? 8 : (vs - 11)/2;
+  sectorsPerCluster = 1UL << sectorsPerClusterShift;
+  fatLength = 1UL << (vs < 27 ? 13 : (vs + 1)/2);
+  fatOffset = fatLength;
+  partitionOffset = 2*fatLength;
+  clusterHeapOffset = 2*fatLength;
+  clusterCount = (sectorCount - 4*fatLength) >> sectorsPerClusterShift;
+  volumeLength = clusterHeapOffset + (clusterCount << sectorsPerClusterShift);
+
+  // make Master Boot Record.  Use fake CHS.
+  memset(secBuf, 0, BYTES_PER_SECTOR);
+  mbr = reinterpret_cast<MbrSector_t*>(secBuf);
+  mbr->part->beginCHS[0] = 1;
+  mbr->part->beginCHS[1] = 1;
+  mbr->part->beginCHS[2] = 0;
+  mbr->part->type = 7;
+  mbr->part->endCHS[0] = 0XFE;
+  mbr->part->endCHS[1] = 0XFF;
+  mbr->part->endCHS[2] = 0XFF;
+  setLe32(mbr->part->relativeSectors, partitionOffset);
+  setLe32(mbr->part->totalSectors, volumeLength);
+  setLe16(mbr->signature, MBR_SIGNATURE);
+  if (!dev->writeSector(0, secBuf)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Partition Boot sector.
+  memset(secBuf, 0, BYTES_PER_SECTOR);
+  pbs = reinterpret_cast<ExFatPbs_t*>(secBuf);
+  pbs->jmpInstruction[0] = 0XEB;
+  pbs->jmpInstruction[1] = 0X76;
+  pbs->jmpInstruction[2] = 0X90;
+  pbs->oemName[0] = 'E';
+  pbs->oemName[1] = 'X';
+  pbs->oemName[2] = 'F';
+  pbs->oemName[3] = 'A';
+  pbs->oemName[4] = 'T';
+  pbs->oemName[5] = ' ';
+  pbs->oemName[6] = ' ';
+  pbs->oemName[7] = ' ';
+  setLe64(pbs->bpb.partitionOffset, partitionOffset);
+  setLe64(pbs->bpb.volumeLength, volumeLength);
+  setLe32(pbs->bpb.fatOffset, fatOffset);
+  setLe32(pbs->bpb.fatLength, fatLength);
+  setLe32(pbs->bpb.clusterHeapOffset, clusterHeapOffset);
+  setLe32(pbs->bpb.clusterCount, clusterCount);
+  setLe32(pbs->bpb.rootDirectoryCluster, ROOT_CLUSTER);
+  setLe32(pbs->bpb.volumeSerialNumber, sectorCount);
+  setLe16(pbs->bpb.fileSystemRevision, 0X100);
+  setLe16(pbs->bpb.volumeFlags, 0);
+  pbs->bpb.bytesPerSectorShift = BYTES_PER_SECTOR_SHIFT;
+  pbs->bpb.sectorsPerClusterShift = sectorsPerClusterShift;
+  pbs->bpb.numberOfFats = 1;
+  pbs->bpb.driveSelect = 0X80;
+  pbs->bpb.percentInUse = 0;
+
+  // Fill boot code like official SDFormatter.
+  for (size_t i = 0; i < sizeof(pbs->bootCode); i++) {
+    pbs->bootCode[i] = 0XF4;
+  }
+  setLe16(pbs->signature, PBR_SIGNATURE);
+  for (size_t i = 0; i < BYTES_PER_SECTOR; i++) {
+    if (i == offsetof(ExFatPbs_t, bpb.volumeFlags[0]) ||
+        i == offsetof(ExFatPbs_t, bpb.volumeFlags[1]) ||
+        i == offsetof(ExFatPbs_t, bpb.percentInUse)) {
+      continue;
+    }
+    checksum = exFatChecksum(checksum, secBuf[i]);
+  }
+  sector = partitionOffset;
+  if (!dev->writeSector(sector, secBuf)  ||
+      !dev->writeSector(sector + BOOT_BACKUP_OFFSET , secBuf)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  sector++;
+  // Write eight Extended Boot Sectors.
+  memset(secBuf, 0, BYTES_PER_SECTOR);
+  setLe16(pbs->signature, PBR_SIGNATURE);
+  for (int j = 0; j < 8; j++) {
+    for (size_t i = 0; i < BYTES_PER_SECTOR; i++) {
+      checksum = exFatChecksum(checksum, secBuf[i]);
+    }
+    if (!dev->writeSector(sector, secBuf)  ||
+        !dev->writeSector(sector + BOOT_BACKUP_OFFSET , secBuf)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    sector++;
+  }
+  // Write OEM Parameter Sector and reserved sector.
+  memset(secBuf, 0, BYTES_PER_SECTOR);
+  for (int j = 0; j < 2; j++) {
+    for (size_t i = 0; i < BYTES_PER_SECTOR; i++) {
+      checksum = exFatChecksum(checksum, secBuf[i]);
+    }
+    if (!dev->writeSector(sector, secBuf)  ||
+        !dev->writeSector(sector + BOOT_BACKUP_OFFSET , secBuf)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    sector++;
+  }
+  // Write Boot CheckSum Sector.
+  for (size_t i = 0; i < BYTES_PER_SECTOR; i += 4) {
+    setLe32(secBuf + i, checksum);
+  }
+  if (!dev->writeSector(sector, secBuf)  ||
+      !dev->writeSector(sector + BOOT_BACKUP_OFFSET , secBuf)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Initialize FAT.
+  writeMsg(pr, "Writing FAT ");
+  sector = partitionOffset + fatOffset;
+  ns = ((clusterCount + 2)*4 + BYTES_PER_SECTOR - 1)/BYTES_PER_SECTOR;
+
+  memset(secBuf, 0, BYTES_PER_SECTOR);
+  // Allocate two reserved clusters, bitmap, upcase, and root clusters.
+  secBuf[0] = 0XF8;
+  for (size_t i = 1; i < 20; i++) {
+    secBuf[i] = 0XFF;
+  }
+  for (uint32_t i = 0; i < ns; i++) {
+    if (i%(ns/32) == 0) {
+      writeMsg(pr, ".");
+    }
+    if (!dev->writeSector(sector + i, secBuf)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (i == 0) {
+      memset(secBuf, 0, BYTES_PER_SECTOR);
+    }
+  }
+  writeMsg(pr, "\r\n");
+  // Write cluster two, bitmap.
+  sector = partitionOffset + clusterHeapOffset;
+  bitmapSize = (clusterCount + 7)/8;
+  ns = (bitmapSize + BYTES_PER_SECTOR - 1)/BYTES_PER_SECTOR;
+  if (ns > sectorsPerCluster) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  memset(secBuf, 0, BYTES_PER_SECTOR);
+  // Allocate clusters for bitmap, upcase, and root.
+  secBuf[0] = 0X7;
+  for (uint32_t i = 0; i < ns; i++) {
+    if (!dev->writeSector(sector + i, secBuf)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (i == 0) {
+      secBuf[0] = 0;
+    }
+  }
+  // Write cluster three, upcase table.
+  writeMsg(pr, "Writing upcase table\r\n");
+  if (!writeUpcase(partitionOffset + clusterHeapOffset + sectorsPerCluster)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (m_upcaseSize > BYTES_PER_SECTOR*sectorsPerCluster) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Initialize first sector of root.
+  writeMsg(pr, "Writing root\r\n");
+  ns = sectorsPerCluster;
+  sector = partitionOffset + clusterHeapOffset + 2*sectorsPerCluster;
+  memset(secBuf, 0, BYTES_PER_SECTOR);
+
+  // Unused Label entry.
+  label = reinterpret_cast<DirLabel_t*>(secBuf);
+  label->type = EXFAT_TYPE_LABEL & 0X7F;
+
+  // bitmap directory entry.
+  dbm = reinterpret_cast<DirBitmap_t*>(secBuf + 32);
+  dbm->type = EXFAT_TYPE_BITMAP;
+  setLe32(dbm->firstCluster, BITMAP_CLUSTER);
+  setLe64(dbm->size, bitmapSize);
+
+  // upcase directory entry.
+  dup = reinterpret_cast<DirUpcase_t*>(secBuf + 64);
+  dup->type = EXFAT_TYPE_UPCASE;
+  setLe32(dup->checksum, m_upcaseChecksum);
+  setLe32(dup->firstCluster, UPCASE_CLUSTER);
+  setLe64(dup->size, m_upcaseSize);
+
+  // Write root, cluster four.
+  for (uint32_t i = 0; i < ns; i++) {
+    if (!dev->writeSector(sector + i, secBuf)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (i == 0) {
+      memset(secBuf, 0, BYTES_PER_SECTOR);
+    }
+  }
+  writeMsg(pr, "Format done\r\n");
+  return true;
+
+ fail:
+  writeMsg(pr, "Format failed\r\n");
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatFormatter::syncUpcase() {
+  uint16_t index = m_upcaseSize & SECTOR_MASK;
+  if (!index) {
+    return true;
+  }
+  for (size_t i = index; i < BYTES_PER_SECTOR; i++) {
+    m_secBuf[i] = 0;
+  }
+  return m_dev->writeSector(m_upcaseSector, m_secBuf);
+}
+//------------------------------------------------------------------------------
+bool ExFatFormatter::writeUpcaseByte(uint8_t b) {
+  uint16_t index = m_upcaseSize & SECTOR_MASK;
+  m_secBuf[index] = b;
+  m_upcaseChecksum = exFatChecksum(m_upcaseChecksum, b);
+  m_upcaseSize++;
+  if (index == SECTOR_MASK) {
+    return m_dev->writeSector(m_upcaseSector++, m_secBuf);
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+bool ExFatFormatter::writeUpcaseUnicode(uint16_t unicode) {
+  return writeUpcaseByte(unicode) && writeUpcaseByte(unicode >> 8);
+}
+//------------------------------------------------------------------------------
+bool ExFatFormatter::writeUpcase(uint32_t sector) {
+  uint32_t n;
+  uint32_t ns;
+  uint32_t ch = 0;
+  uint16_t uc;
+
+  m_upcaseSize = 0;
+  m_upcaseChecksum = 0;
+  m_upcaseSector = sector;
+
+  while (ch < 0X10000) {
+    uc = toUpcase(ch);
+    if (uc != ch) {
+      if (!writeUpcaseUnicode(uc)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      ch++;
+    } else {
+      for (n = ch + 1; n < 0X10000 && n == toUpcase(n); n++) {}
+      ns = n - ch;
+      if (ns >= MINIMUM_UPCASE_SKIP) {
+        if (!writeUpcaseUnicode(0XFFFF) || !writeUpcaseUnicode(ns)) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+        ch = n;
+      } else {
+        while (ch < n) {
+          if (!writeUpcaseUnicode(ch++)) {
+            DBG_FAIL_MACRO;
+            goto fail;
+          }
+        }
+      }
+    }
+  }
+  if (!syncUpcase()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  return false;
+}

+ 55 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatFormatter.h

@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef ExFatFormatter_h
+#define ExFatFormatter_h
+#include "../common/FsBlockDevice.h"
+/**
+ * \class ExFatFormatter
+ * \brief Format an exFAT volume.
+ */
+class ExFatFormatter {
+ public:
+  /**
+   * Format an exFAT volume.
+   *
+   * \param[in] dev Block device for volume.
+   * \param[in] secBuf buffer for writing to volume.
+   * \param[in] pr Print device for progress output.
+   *
+   * \return true for success or false for failure.
+   */
+  bool format(FsBlockDevice* dev, uint8_t* secBuf, print_t* pr = nullptr);
+ private:
+  bool syncUpcase();
+  bool writeUpcase(uint32_t sector);
+  bool writeUpcaseByte(uint8_t b);
+  bool writeUpcaseUnicode(uint16_t unicode);
+  uint32_t m_upcaseSector;
+  uint32_t m_upcaseChecksum;
+  uint32_t m_upcaseSize;
+  FsBlockDevice* m_dev;
+  uint8_t* m_secBuf;
+};
+#endif  // ExFatFormatter_h

+ 29 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatLib.h

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef ExFatLib_h
+#define ExFatLib_h
+#include "ExFatVolume.h"
+#include "ExFatFormatter.h"
+#endif  // ExFatLib_h

+ 194 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatName.cpp

@@ -0,0 +1,194 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "ExFatName.cpp"
+#include "../common/DebugMacros.h"
+#include "../common/upcase.h"
+#include "../common/FsUtf.h"
+#include "ExFatLib.h"
+//------------------------------------------------------------------------------
+static char toUpper(char c) {
+  return 'a' <= c && c <= 'z' ? c - 'a' + 'A' : c;
+}
+//------------------------------------------------------------------------------
+inline uint16_t exFatHash(char c, uint16_t hash) {
+  uint8_t u = toUpper(c);
+  hash = ((hash << 15) | (hash >> 1)) + u;
+  hash = ((hash << 15) | (hash >> 1));
+  return hash;
+}
+//------------------------------------------------------------------------------
+inline uint16_t exFatHash(uint16_t u, uint16_t hash) {
+  uint16_t c = toUpcase(u);
+  hash = ((hash << 15) | (hash >> 1)) + (c & 0XFF);
+  hash = ((hash << 15) | (hash >> 1)) + (c >> 8);
+  return hash;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::cmpName(const DirName_t* dirName, ExName_t* fname) {
+  for (uint8_t i = 0; i < 15; i++) {
+    uint16_t u = getLe16(dirName->unicode + 2*i);
+    if (fname->atEnd()) {
+      return u == 0;
+    }
+#if USE_UTF8_LONG_NAMES
+    uint16_t cp = fname->get16();
+    if (toUpcase(cp) != toUpcase(u)) {
+       return false;
+    }
+#else  // USE_UTF8_LONG_NAMES
+    char c = fname->getch();
+    if (u >= 0x7F || toUpper(c) != toUpper(u)) {
+      return false;
+    }
+#endif  // USE_UTF8_LONG_NAMES
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+size_t ExFatFile::getName7(char* name, size_t count) {
+  DirName_t* dn;
+  size_t n = 0;
+  if (!isOpen()) {
+      DBG_FAIL_MACRO;
+      goto fail;
+  }
+  for (uint8_t is = 2; is <= m_setCount; is++) {
+    dn = reinterpret_cast<DirName_t*>
+         (dirCache(is, FsCache::CACHE_FOR_READ));
+    if (!dn || dn->type != EXFAT_TYPE_NAME) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    for (uint8_t in = 0; in < 15; in++) {
+      uint16_t c = getLe16(dn->unicode + 2*in);
+      if (c == 0) {
+        goto done;
+      }
+      if ((n + 1) >= count) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      name[n++] = c < 0X7F ? c : '?';
+    }
+  }
+ done:
+  name[n] = 0;
+  return n;
+
+ fail:
+  *name = 0;
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t ExFatFile::getName8(char* name, size_t count) {
+  char* end = name + count;
+  char* str = name;
+  char* ptr;
+  DirName_t* dn;
+  uint16_t hs = 0;
+  uint32_t cp;
+  if (!isOpen()) {
+      DBG_FAIL_MACRO;
+      goto fail;
+  }
+  for (uint8_t is = 2; is <= m_setCount; is++) {
+    dn = reinterpret_cast<DirName_t*>
+         (dirCache(is, FsCache::CACHE_FOR_READ));
+    if (!dn || dn->type != EXFAT_TYPE_NAME) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    for (uint8_t in = 0; in < 15; in++) {
+      uint16_t c = getLe16(dn->unicode + 2*in);
+      if (hs) {
+        if (!FsUtf::isLowSurrogate(c)) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+        cp = FsUtf::u16ToCp(hs, c);
+        hs = 0;
+      } else if (!FsUtf::isSurrogate(c)) {
+        if (c == 0) {
+          goto done;
+        }
+        cp = c;
+      } else if (FsUtf::isHighSurrogate(c)) {
+        hs = c;
+        continue;
+      } else {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      // Save space for zero byte.
+      ptr = FsUtf::cpToMb(cp, str, end - 1);
+      if (!ptr) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      str = ptr;
+    }
+  }
+ done:
+  *str = '\0';
+  return str - name;
+
+ fail:
+  *name = 0;
+  return 0;
+}
+//------------------------------------------------------------------------------
+bool ExFatFile::hashName(ExName_t* fname) {
+  uint16_t hash = 0;
+  fname->reset();
+#if USE_UTF8_LONG_NAMES
+  fname->nameLength = 0;
+  while (!fname->atEnd()) {
+    uint16_t u = fname->get16();
+    if (u == 0XFFFF) {
+    DBG_FAIL_MACRO;
+      goto fail;
+    }
+    hash = exFatHash(u, hash);
+    fname->nameLength++;
+  }
+#else  // USE_UTF8_LONG_NAMES
+  while (!fname->atEnd()) {
+    // Convert to byte for smaller exFatHash.
+    char c = fname->getch();
+    hash = exFatHash(c, hash);
+  }
+  fname->nameLength = fname->end - fname->begin;
+#endif  // USE_UTF8_LONG_NAMES
+  fname->nameHash = hash;
+  if (!fname->nameLength || fname->nameLength > EXFAT_MAX_NAME_LENGTH) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  return false;
+}
+

+ 322 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatPartition.cpp

@@ -0,0 +1,322 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "ExFatPartition.cpp"
+#include "../common/DebugMacros.h"
+#include "../common/PartitionTable.h"
+#include "ExFatLib.h"
+//------------------------------------------------------------------------------
+// return 0 if error, 1 if no space, else start cluster.
+uint32_t ExFatPartition::bitmapFind(uint32_t cluster, uint32_t count) {
+  uint32_t start = cluster ? cluster - 2 : m_bitmapStart;
+  if (start >= m_clusterCount) {
+    start = 0;
+  }
+  uint32_t endAlloc = start;
+  uint32_t bgnAlloc = start;
+  uint16_t sectorSize = 1 << m_bytesPerSectorShift;
+  size_t i = (start >> 3) & (sectorSize - 1);
+  uint8_t* cache;
+  uint8_t mask = 1 << (start & 7);
+  while (true) {
+    uint32_t sector = m_clusterHeapStartSector +
+                     (endAlloc >> (m_bytesPerSectorShift + 3));
+    cache = bitmapCachePrepare(sector, FsCache::CACHE_FOR_READ);
+    if (!cache) {
+      return 0;
+    }
+    for (; i < sectorSize; i++) {
+      for (; mask; mask <<= 1) {
+        endAlloc++;
+        if (!(mask & cache[i])) {
+          if ((endAlloc - bgnAlloc) == count) {
+            if (cluster == 0 && count == 1) {
+              // Start at found sector.  bitmapModify may increase this.
+              m_bitmapStart = bgnAlloc;
+            }
+            return bgnAlloc + 2;
+          }
+        } else {
+          bgnAlloc = endAlloc;
+        }
+        if (endAlloc == start) {
+          return 1;
+        }
+        if (endAlloc >= m_clusterCount) {
+          endAlloc = bgnAlloc = 0;
+          i = sectorSize;
+          break;
+        }
+      }
+      mask = 1;
+    }
+    i = 0;
+  }
+  return 0;
+}
+//------------------------------------------------------------------------------
+bool ExFatPartition::bitmapModify(uint32_t cluster,
+                                  uint32_t count, bool value) {
+  uint32_t sector;
+  uint32_t start = cluster - 2;
+  size_t i;
+  uint8_t* cache;
+  uint8_t mask;
+  cluster -= 2;
+  if ((start + count) > m_clusterCount) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (value) {
+    if (start  <= m_bitmapStart && m_bitmapStart < (start + count)) {
+      m_bitmapStart = (start + count) < m_clusterCount ? start + count : 0;
+    }
+  } else {
+    if (start < m_bitmapStart) {
+      m_bitmapStart = start;
+    }
+  }
+  mask = 1 << (start & 7);
+  sector = m_clusterHeapStartSector +
+                   (start >> (m_bytesPerSectorShift + 3));
+  i = (start >> 3) & m_sectorMask;
+  while (true) {
+    cache = bitmapCachePrepare(sector++, FsCache::CACHE_FOR_WRITE);
+    if (!cache) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    for (; i < m_bytesPerSector; i++) {
+      for (; mask; mask <<= 1) {
+        if (value == static_cast<bool>(cache[i] & mask)) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+        cache[i] ^= mask;
+        if (--count == 0) {
+          return true;
+        }
+      }
+      mask = 1;
+    }
+    i = 0;
+  }
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+uint32_t ExFatPartition::chainSize(uint32_t cluster) {
+  uint32_t n = 0;
+  int8_t status;
+  do {
+    status = fatGet(cluster, & cluster);
+    if (status < 0) return 0;
+    n++;
+  } while (status);
+  return n;
+}
+//------------------------------------------------------------------------------
+uint8_t* ExFatPartition::dirCache(DirPos_t* pos, uint8_t options) {
+  uint32_t sector = clusterStartSector(pos->cluster);
+  sector += (m_clusterMask & pos->position) >> m_bytesPerSectorShift;
+  uint8_t* cache = dataCachePrepare(sector, options);
+  return cache ? cache + (pos->position & m_sectorMask) : nullptr;
+}
+//------------------------------------------------------------------------------
+// return -1 error, 0 EOC, 1 OK
+int8_t ExFatPartition::dirSeek(DirPos_t* pos, uint32_t offset) {
+  int8_t status;
+  uint32_t tmp = (m_clusterMask & pos->position) + offset;
+  pos->position += offset;
+  tmp >>= bytesPerClusterShift();
+  while (tmp--) {
+    if (pos->isContiguous) {
+      pos->cluster++;
+    } else {
+      status = fatGet(pos->cluster, &pos->cluster);
+      if (status != 1) {
+        return status;
+      }
+    }
+  }
+  return 1;
+}
+//------------------------------------------------------------------------------
+// return -1 error, 0 EOC, 1 OK
+int8_t ExFatPartition::fatGet(uint32_t cluster, uint32_t* value) {
+  uint8_t* cache;
+  uint32_t next;
+  uint32_t sector;
+
+  if (cluster > (m_clusterCount + 1)) {
+    DBG_FAIL_MACRO;
+    return -1;
+  }
+  sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 2));
+
+  cache = dataCachePrepare(sector, FsCache::CACHE_FOR_READ);
+  if (!cache) {
+    return -1;
+  }
+  next = getLe32(cache + ((cluster << 2) & m_sectorMask));
+  if (next == EXFAT_EOC) {
+    return 0;
+  }
+  *value = next;
+  return 1;
+}
+//------------------------------------------------------------------------------
+bool ExFatPartition::fatPut(uint32_t cluster, uint32_t value) {
+  uint32_t sector;
+  uint8_t* cache;
+  if (cluster < 2 || cluster > (m_clusterCount + 1)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 2));
+  cache = dataCachePrepare(sector, FsCache::CACHE_FOR_WRITE);
+  if (!cache) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  setLe32(cache + ((cluster << 2) & m_sectorMask), value);
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool ExFatPartition::freeChain(uint32_t cluster) {
+  uint32_t next;
+  uint32_t start = cluster;
+  int8_t status;
+  do {
+    status = fatGet(cluster, &next);
+    if (status < 0) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (!fatPut(cluster, 0)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (status == 0 || (cluster + 1) != next) {
+      if (!bitmapModify(start, cluster - start + 1, 0)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      start = next;
+    }
+    cluster = next;
+  } while (status);
+
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+int32_t ExFatPartition::freeClusterCount() {
+  uint32_t nc = 0;
+  uint32_t sector = m_clusterHeapStartSector;
+  uint32_t usedCount = 0;
+  uint8_t* cache;
+
+  while (true) {
+    cache = dataCachePrepare(sector++, FsCache::CACHE_FOR_READ);
+    if (!cache) {
+      return -1;
+    }
+    for (size_t i = 0; i < m_bytesPerSector; i++) {
+      if (cache[i] == 0XFF) {
+        usedCount+= 8;
+      } else if (cache[i]) {
+        for (uint8_t mask = 1; mask ; mask <<=1) {
+          if ((mask & cache[i])) {
+            usedCount++;
+          }
+        }
+      }
+      nc += 8;
+      if (nc >= m_clusterCount) {
+        return m_clusterCount - usedCount;
+      }
+    }
+  }
+}
+//------------------------------------------------------------------------------
+bool ExFatPartition::init(FsBlockDevice* dev, uint8_t part, uint32_t volStart) {
+  pbs_t* pbs;
+  BpbExFat_t* bpb;
+  m_fatType = 0;
+  m_blockDev = dev;
+  cacheInit(m_blockDev);
+  // if part == 0 assume super floppy with FAT boot sector in sector zero
+  // if part > 0 read MBR / GPT partition table
+  if (part) {
+    volStart = partitionTableGetVolumeStartSector(m_dataCache, part);
+
+    if (!volStart) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+  pbs = reinterpret_cast<pbs_t*>
+        (dataCachePrepare(volStart, FsCache::CACHE_FOR_READ));
+  if (!pbs) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (strncmp(pbs->oemName, "EXFAT", 5)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  bpb = reinterpret_cast<BpbExFat_t*>(pbs->bpb);
+  if (bpb->bytesPerSectorShift != m_bytesPerSectorShift) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  m_fatStartSector = volStart + getLe32(bpb->fatOffset);
+  m_fatLength = getLe32(bpb->fatLength);
+  m_clusterHeapStartSector = volStart + getLe32(bpb->clusterHeapOffset);
+  m_clusterCount = getLe32(bpb->clusterCount);
+  m_rootDirectoryCluster = getLe32(bpb->rootDirectoryCluster);
+  m_sectorsPerClusterShift = bpb->sectorsPerClusterShift;
+  m_bytesPerCluster = 1UL << (m_bytesPerSectorShift + m_sectorsPerClusterShift);
+  m_clusterMask = m_bytesPerCluster - 1;
+  // Set m_bitmapStart to first free cluster.
+  m_bitmapStart = 0;
+  bitmapFind(0, 1);
+  m_fatType = FAT_TYPE_EXFAT;
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+uint32_t ExFatPartition::rootLength() {
+  uint32_t nc = chainSize(m_rootDirectoryCluster);
+  return nc << bytesPerClusterShift();
+}

+ 231 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatPartition.h

@@ -0,0 +1,231 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef ExFatPartition_h
+#define ExFatPartition_h
+/**
+ * \file
+ * \brief ExFatPartition include file.
+ */
+#include "../common/SysCall.h"
+#include "../common/FsBlockDevice.h"
+#include "../common/FsCache.h"
+#include "../common/FsStructs.h"
+/** Set EXFAT_READ_ONLY non-zero for read only */
+#ifndef EXFAT_READ_ONLY
+#define EXFAT_READ_ONLY 0
+#endif  // EXFAT_READ_ONLY
+/** Type for exFAT partition */
+const uint8_t FAT_TYPE_EXFAT = 64;
+
+class ExFatFile;
+//------------------------------------------------------------------------------
+/**
+ * \struct DirPos_t
+ * \brief Internal type for position in directory file.
+ */
+struct DirPos_t {
+  /** current cluster */
+  uint32_t cluster;
+  /** offset */
+  uint32_t position;
+  /** directory is contiguous */
+  bool     isContiguous;
+};
+//==============================================================================
+/**
+ * \class ExFatPartition
+ * \brief Access exFat partitions on raw file devices.
+ */
+class ExFatPartition {
+ public:
+  ExFatPartition() {}
+  /** \return the number of bytes in a cluster. */
+  uint32_t bytesPerCluster() const {return m_bytesPerCluster;}
+  /** \return the power of two for bytesPerCluster. */
+  uint8_t bytesPerClusterShift() const {
+    return m_bytesPerSectorShift + m_sectorsPerClusterShift;
+  }
+  /** \return the number of bytes in a sector. */
+  uint16_t bytesPerSector() const {return m_bytesPerSector;}
+  /** \return the power of two for bytesPerSector. */
+  uint8_t bytesPerSectorShift() const {return m_bytesPerSectorShift;}
+
+  /** Clear the cache and returns a pointer to the cache.  Not for normal apps.
+   * \return A pointer to the cache buffer or zero if an error occurs.
+   */
+  uint8_t* cacheClear() {
+    return m_dataCache.clear();
+  }
+  /** \return the cluster count for the partition. */
+  uint32_t clusterCount() const {return m_clusterCount;}
+  /** \return the cluster heap start sector. */
+  uint32_t clusterHeapStartSector() const {return m_clusterHeapStartSector;}
+  /** End access to volume
+   * \return pointer to sector size buffer for format.
+   */
+  uint8_t* end() {
+    m_fatType = 0;
+    return cacheClear();
+  }
+  /** \return the FAT length in sectors */
+  uint32_t fatLength() const {return m_fatLength;}
+  /** \return the FAT start sector number. */
+  uint32_t fatStartSector() const {return m_fatStartSector;}
+  /** \return Type FAT_TYPE_EXFAT for exFAT partition or zero for error. */
+  uint8_t fatType() const {return m_fatType;}
+  /** \return free cluster count or -1 if an error occurs. */
+  int32_t freeClusterCount();
+  /** Initialize a exFAT partition.
+   * \param[in] dev The blockDevice for the partition.
+   * \param[in] part The partition to be used.  Legal values for \a part are
+   * 1-4 to use the corresponding partition on a device formatted with
+   * a MBR, Master Boot Record, or zero if the device is formatted as
+   * a super floppy with the FAT boot sector in sector volStart.
+   * \param[in] volStart location of volume if part is zero.
+   *
+   * \return true for success or false for failure.
+   */
+  bool init(FsBlockDevice* dev, uint8_t part, uint32_t volStart = 0);
+  /**
+   * Check for device busy.
+   *
+   * \return true if busy else false.
+   */
+  bool isBusy() {return m_blockDev->isBusy();}
+  /** \return the root directory start cluster number. */
+  uint32_t rootDirectoryCluster() const {return m_rootDirectoryCluster;}
+  /** \return the root directory length. */
+  uint32_t rootLength();
+  /** \return the number of sectors in a cluster. */
+  uint32_t sectorsPerCluster() const {return 1UL << m_sectorsPerClusterShift;}
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+  uint32_t __attribute__((error("use sectorsPerCluster()"))) blocksPerCluster();
+#endif  // DOXYGEN_SHOULD_SKIP_THIS
+  /** \return the power of two for sectors per cluster. */
+  uint8_t  sectorsPerClusterShift() const {return m_sectorsPerClusterShift;}
+  //----------------------------------------------------------------------------
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+  void checkUpcase(print_t* pr);
+  bool printDir(print_t* pr, ExFatFile* file);
+  void dmpBitmap(print_t* pr);
+  void dmpCluster(print_t* pr, uint32_t cluster,
+                  uint32_t offset, uint32_t count);
+  void dmpFat(print_t* pr, uint32_t start, uint32_t count);
+  void dmpSector(print_t* pr, uint32_t sector);
+  bool printVolInfo(print_t* pr);
+  void printFat(print_t* pr);
+  void printUpcase(print_t* pr);
+#endif  // DOXYGEN_SHOULD_SKIP_THIS
+  //----------------------------------------------------------------------------
+ private:
+  /** ExFatFile allowed access to private members. */
+  friend class ExFatFile;
+  uint32_t bitmapFind(uint32_t cluster, uint32_t count);
+  bool bitmapModify(uint32_t cluster, uint32_t count, bool value);
+  //----------------------------------------------------------------------------
+  // Cache functions.
+  uint8_t* bitmapCachePrepare(uint32_t sector, uint8_t option) {
+#if USE_EXFAT_BITMAP_CACHE
+    return m_bitmapCache.prepare(sector, option);
+#else  // USE_EXFAT_BITMAP_CACHE
+    return m_dataCache.prepare(sector, option);
+#endif  // USE_EXFAT_BITMAP_CACHE
+  }
+  void cacheInit(FsBlockDevice* dev) {
+#if USE_EXFAT_BITMAP_CACHE
+    m_bitmapCache.init(dev);
+#endif  // USE_EXFAT_BITMAP_CACHE
+    m_dataCache.init(dev);
+  }
+  bool cacheSync() {
+#if USE_EXFAT_BITMAP_CACHE
+    return m_bitmapCache.sync() && m_dataCache.sync() && syncDevice();
+#else  // USE_EXFAT_BITMAP_CACHE
+    return m_dataCache.sync() && syncDevice();
+#endif  // USE_EXFAT_BITMAP_CACHE
+  }
+  void dataCacheDirty() {m_dataCache.dirty();}
+  void dataCacheInvalidate() {m_dataCache.invalidate();}
+  uint8_t* dataCachePrepare(uint32_t sector, uint8_t option) {
+    return m_dataCache.prepare(sector, option);
+  }
+  uint32_t dataCacheSector() {return m_dataCache.sector();}
+  bool dataCacheSync() {return m_dataCache.sync();}
+  //----------------------------------------------------------------------------
+  uint32_t clusterMask() const {return m_clusterMask;}
+  uint32_t clusterStartSector(uint32_t cluster) {
+    return m_clusterHeapStartSector +
+           ((cluster - 2) << m_sectorsPerClusterShift);
+  }
+  uint8_t* dirCache(DirPos_t* pos, uint8_t options);
+  int8_t dirSeek(DirPos_t* pos, uint32_t offset);
+  int8_t fatGet(uint32_t cluster, uint32_t* value);
+  bool fatPut(uint32_t cluster, uint32_t value);
+  uint32_t chainSize(uint32_t cluster);
+  bool freeChain(uint32_t cluster);
+  uint16_t sectorMask() const {return m_sectorMask;}
+  bool syncDevice() {
+    return m_blockDev->syncDevice();
+  }
+  bool cacheSafeRead(uint32_t sector, uint8_t* dst) {
+    return m_dataCache.cacheSafeRead(sector, dst);
+  }
+  bool cacheSafeWrite(uint32_t sector, const uint8_t* src) {
+    return m_dataCache.cacheSafeWrite(sector, src);
+  }
+  bool cacheSafeRead(uint32_t sector, uint8_t* dst, size_t count) {
+    return m_dataCache.cacheSafeRead(sector, dst, count);
+  }
+  bool cacheSafeWrite(uint32_t sector, const uint8_t* src, size_t count) {
+     return m_dataCache.cacheSafeWrite(sector, src, count);
+  }
+  bool readSector(uint32_t sector, uint8_t* dst) {
+    return m_blockDev->readSector(sector, dst);
+  }
+  bool writeSector(uint32_t sector, const uint8_t* src) {
+    return m_blockDev->writeSector(sector, src);
+  }
+  //----------------------------------------------------------------------------
+  static const uint8_t  m_bytesPerSectorShift = 9;
+  static const uint16_t m_bytesPerSector = 1 << m_bytesPerSectorShift;
+  static const uint16_t m_sectorMask = m_bytesPerSector - 1;
+  //----------------------------------------------------------------------------
+#if USE_EXFAT_BITMAP_CACHE
+  FsCache  m_bitmapCache;
+#endif  // USE_EXFAT_BITMAP_CACHE
+  FsCache  m_dataCache;
+  uint32_t m_bitmapStart;
+  uint32_t m_fatStartSector;
+  uint32_t m_fatLength;
+  uint32_t m_clusterHeapStartSector;
+  uint32_t m_clusterCount;
+  uint32_t m_rootDirectoryCluster;
+  uint32_t m_clusterMask;
+  uint32_t m_bytesPerCluster;
+  FsBlockDevice* m_blockDev;
+  uint8_t  m_fatType = 0;
+  uint8_t  m_sectorsPerClusterShift;
+};
+#endif  // ExFatPartition_h

+ 45 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatVolume.cpp

@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "ExFatVolume.cpp"
+#include "../common/DebugMacros.h"
+#include "ExFatLib.h"
+ExFatVolume* ExFatVolume::m_cwv = nullptr;
+//-----------------------------------------------------------------------------
+bool ExFatVolume::chdir(const char* path) {
+  ExFatFile dir;
+  if (!dir.open(vwd(), path, O_RDONLY)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (!dir.isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  m_vwd = dir;
+  return true;
+
+ fail:
+  return false;
+}

+ 357 - 0
lib/SdFat_NoArduino/src/ExFatLib/ExFatVolume.h

@@ -0,0 +1,357 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef ExFatVolume_h
+#define ExFatVolume_h
+#include "ExFatFile.h"
+//==============================================================================
+/**
+ * \class ExFatVolume
+ * \brief exFAT volume.
+ */
+class ExFatVolume : public ExFatPartition {
+ public:
+  ExFatVolume() {}
+  /** Get file's user settable attributes.
+   * \param[in] path path to file.
+   * \return user settable file attributes for success else -1.
+   */
+  int attrib(const char* path) {
+    ExFatFile tmpFile;
+    return tmpFile.open(this, path, O_RDONLY) ? tmpFile.attrib() : -1;
+  }
+  /** Set file's user settable attributes.
+   * \param[in] path path to file.
+   * \param[in] bits bit-wise or of selected attributes: FS_ATTRIB_READ_ONLY,
+   *            FS_ATTRIB_HIDDEN, FS_ATTRIB_SYSTEM, FS_ATTRIB_ARCHIVE.
+   *
+   * \return true for success or false for failure.
+   */
+  bool attrib(const char* path, uint8_t bits) {
+    ExFatFile tmpFile;
+    return tmpFile.open(this, path, O_RDONLY) ? tmpFile.attrib(bits) : false;
+  }
+  /**
+   * Initialize an FatVolume object.
+   * \param[in] dev Device block driver.
+   * \param[in] setCwv Set current working volume if true.
+   * \param[in] part Partition to initialize.
+   * \param[in] volStart Start sector of volume if part is zero.
+   * \return true for success or false for failure.
+   */
+  bool begin(FsBlockDevice* dev, bool setCwv = true,
+             uint8_t part = 1, uint32_t volStart = 0) {
+    if (!init(dev, part, volStart)) {
+      return false;
+    }
+    if (!chdir()) {
+      return false;
+    }
+    if (setCwv || !m_cwv) {
+      m_cwv = this;
+    }
+    return true;
+  }
+  /**
+   * Set volume working directory to root.
+   * \return true for success or false for failure.
+   */
+  bool chdir() {
+    m_vwd.close();
+    return m_vwd.openRoot(this);
+  }
+  /**
+   * Set volume working directory.
+   * \param[in] path Path for volume working directory.
+   * \return true for success or false for failure.
+   */
+  bool chdir(const char* path);
+
+  /** Change global working volume to this volume. */
+  void chvol() {m_cwv = this;}
+
+  /**
+   * Test for the existence of a file.
+   *
+   * \param[in] path Path of the file to be tested for.
+   *
+   * \return true if the file exists else false.
+   */
+  bool exists(const char* path) {
+    ExFatFile tmp;
+    return tmp.open(this, path, O_RDONLY);
+  }
+  //----------------------------------------------------------------------------
+  /** List the directory contents of the root directory.
+   *
+   * \param[in] pr Print stream for list.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(print_t* pr, uint8_t flags = 0) {
+    return m_vwd.ls(pr, flags);
+  }
+  /** List the contents of a directory.
+   *
+   * \param[in] pr Print stream for list.
+   *
+   * \param[in] path directory to list.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(print_t* pr, const char* path, uint8_t flags) {
+    ExFatFile dir;
+    return dir.open(this, path, O_RDONLY) && dir.ls(pr, flags);
+  }
+  /** Make a subdirectory in the volume root directory.
+   *
+   * \param[in] path A path with a valid 8.3 DOS name for the subdirectory.
+   *
+   * \param[in] pFlag Create missing parent directories if true.
+   *
+   * \return true for success or false for failure.
+   */
+  bool mkdir(const char* path, bool pFlag = true) {
+    ExFatFile sub;
+    return sub.mkdir(vwd(), path, pFlag);
+  }
+  /** open a file
+   *
+   * \param[in] path location of file to be opened.
+   * \param[in] oflag open flags.
+   * \return a ExFile object.
+   */
+  ExFile open(const char* path, oflag_t oflag = O_RDONLY) {
+    ExFile tmpFile;
+    tmpFile.open(this, path, oflag);
+    return tmpFile;
+  }
+  /** Remove a file from the volume root directory.
+   *
+   * \param[in] path A path with a valid 8.3 DOS name for the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool remove(const char* path) {
+    ExFatFile tmp;
+    return tmp.open(this, path, O_WRONLY) && tmp.remove();
+  }
+  /** Rename a file or subdirectory.
+   *
+   * \param[in] oldPath Path name to the file or subdirectory to be renamed.
+   *
+   * \param[in] newPath New path name of the file or subdirectory.
+   *
+   * The \a newPath object must not exist before the rename call.
+   *
+   * The file to be renamed must not be open.  The directory entry may be
+   * moved and file system corruption could occur if the file is accessed by
+   * a file object that was opened before the rename() call.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rename(const char* oldPath, const char* newPath) {
+    ExFatFile file;
+    return file.open(vwd(), oldPath, O_RDONLY) && file.rename(vwd(), newPath);
+  }
+  /** Remove a subdirectory from the volume's working directory.
+   *
+   * \param[in] path A path with a valid 8.3 DOS name for the subdirectory.
+   *
+   * The subdirectory file will be removed only if it is empty.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rmdir(const char* path) {
+    ExFatFile sub;
+    return sub.open(this, path, O_RDONLY) && sub.rmdir();
+  }
+  /** Truncate a file to a specified length.  The current file position
+   * will be at the new EOF.
+   *
+   * \param[in] path A path with a valid 8.3 DOS name for the file.
+   * \param[in] length The desired length for the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool truncate(const char* path, uint64_t length) {
+    ExFatFile file;
+    if (!file.open(this, path, O_WRONLY)) {
+      return false;
+    }
+    return file.truncate(length);
+  }
+#if ENABLE_ARDUINO_SERIAL
+  /** List the directory contents of the root directory to Serial.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls() {
+    return ls(&Serial);
+  }
+   /** List the directory contents of the volume root to Serial.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(uint8_t flags) {
+    return ls(&Serial, flags);
+  }
+  /** List the directory contents of a directory to Serial.
+   *
+   * \param[in] path directory to list.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(const char* path, uint8_t flags = 0) {
+    return ls(&Serial, path, flags);
+  }
+#endif  // ENABLE_ARDUINO_SERIAL
+#if ENABLE_ARDUINO_STRING
+  /**
+   * Set volume working directory.
+   * \param[in] path Path for volume working directory.
+   * \return true for success or false for failure.
+   */
+  bool chdir(const String& path) {
+    return chdir(path.c_str());
+  }
+  /** Test for the existence of a file in a directory
+   *
+   * \param[in] path Path of the file to be tested for.
+   *
+   * \return true if the file exists else false.
+   */
+  bool exists(const String &path) {
+    return exists(path.c_str());
+  }
+  /** Make a subdirectory in the volume root directory.
+   *
+   * \param[in] path A path with a valid 8.3 DOS name for the subdirectory.
+   *
+   * \param[in] pFlag Create missing parent directories if true.
+   *
+   * \return true for success or false for failure.
+   */
+  bool mkdir(const String &path, bool pFlag = true) {
+    return mkdir(path.c_str(), pFlag);
+  }
+  /** open a file
+   *
+   * \param[in] path location of file to be opened.
+   * \param[in] oflag open oflag flags.
+   * \return a ExFile object.
+   */
+  ExFile open(const String &path, oflag_t oflag = O_RDONLY) {
+    return open(path.c_str(), oflag);
+  }
+  /** Remove a file from the volume root directory.
+   *
+   * \param[in] path A path with a valid name for the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool remove(const String& path) {
+    return remove(path.c_str());
+  }
+  /** Rename a file or subdirectory.
+   *
+   * \param[in] oldPath Path name to the file or subdirectory to be renamed.
+   *
+   * \param[in] newPath New path name of the file or subdirectory.
+   *
+   * The \a newPath object must not exist before the rename call.
+   *
+   * The file to be renamed must not be open.  The directory entry may be
+   * moved and file system corruption could occur if the file is accessed by
+   * a file object that was opened before the rename() call.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rename(const String& oldPath, const String& newPath) {
+    return rename(oldPath.c_str(), newPath.c_str());
+  }
+  /** Remove a subdirectory from the volume's working directory.
+   *
+   * \param[in] path A path with a valid name for the subdirectory.
+   *
+   * The subdirectory file will be removed only if it is empty.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rmdir(const String& path) {
+    return rmdir(path.c_str());
+  }
+  /** Truncate a file to a specified length.  The current file position
+   * will be at the new EOF.
+   *
+   * \param[in] path A path with a valid name for the file.
+   * \param[in] length The desired length for the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool truncate(const String& path, uint64_t length) {
+    return truncate(path.c_str(), length);
+  }
+#endif  // ENABLE_ARDUINO_STRING
+
+ private:
+  friend ExFatFile;
+  static ExFatVolume* cwv() {return m_cwv;}
+  ExFatFile* vwd() {return &m_vwd;}
+  static ExFatVolume* m_cwv;
+  ExFatFile m_vwd;
+};
+#endif  // ExFatVolume_h

+ 268 - 0
lib/SdFat_NoArduino/src/FatLib/FatDbg.cpp

@@ -0,0 +1,268 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "FatLib.h"
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+//------------------------------------------------------------------------------
+static uint16_t getLfnChar(DirLfn_t* ldir, uint8_t i) {
+  if (i < 5) {
+    return getLe16(ldir->unicode1 + 2*i);
+  } else if (i < 11) {
+    return getLe16(ldir->unicode2 + 2*i - 10);
+  } else if (i < 13) {
+    return getLe16(ldir->unicode3 + 2*i - 22);
+  }
+  return 0;
+}
+//------------------------------------------------------------------------------
+static void printHex(print_t* pr, uint8_t h) {
+  if (h < 16) {
+    pr->write('0');
+  }
+  pr->print(h, HEX);
+}
+//------------------------------------------------------------------------------
+static void printHex(print_t* pr, uint8_t w, uint16_t h) {
+  char buf[5];
+  char* ptr = buf + sizeof(buf);
+  *--ptr = 0;
+  for (uint8_t i = 0; i < w; i++) {
+    char c = h & 0XF;
+    *--ptr = c < 10 ? c + '0' : c + 'A' - 10;
+    h >>= 4;
+  }
+  pr->write(ptr);
+}
+//------------------------------------------------------------------------------
+static void printHex(print_t* pr, uint16_t val) {
+  bool space = true;
+  for (uint8_t i = 0; i < 4; i++) {
+    uint8_t h = (val >> (12 - 4*i)) & 15;
+    if (h || i == 3) {
+      space = false;
+    }
+    if (space) {
+      pr->write(' ');
+    } else {
+      pr->print(h, HEX);
+    }
+  }
+}
+//------------------------------------------------------------------------------
+static void printHex(print_t* pr, uint32_t val) {
+  bool space = true;
+  for (uint8_t i = 0; i < 8; i++) {
+    uint8_t h = (val >> (28 - 4*i)) & 15;
+    if (h || i == 7) {
+      space = false;
+    }
+    if (space) {
+      pr->write(' ');
+    } else {
+      pr->print(h, HEX);
+    }
+  }
+}
+//------------------------------------------------------------------------------
+template<typename Uint>
+static void printHexLn(print_t* pr, Uint val) {
+  printHex(pr, val);
+  pr->println();
+}
+//------------------------------------------------------------------------------
+static bool printFatDir(print_t* pr, DirFat_t* dir) {
+  DirLfn_t* ldir = reinterpret_cast<DirLfn_t*>(dir);
+  if (!dir->name[0]) {
+    pr->println(F("Unused"));
+    return false;
+  } else if (dir->name[0] == FAT_NAME_DELETED) {
+    pr->println(F("Deleted"));
+  } else if (isFatFileOrSubdir(dir)) {
+    pr->print(F("SFN: "));
+    for (uint8_t i = 0; i < 11; i++) {
+      printHex(pr, dir->name[i]);
+      pr->write(' ');
+    }
+    pr->write(' ');
+    pr->write(dir->name, 11);
+    pr->println();
+    pr->print(F("attributes: 0X"));
+    printHexLn(pr, dir->attributes);
+    pr->print(F("caseFlags: 0X"));
+    printHexLn(pr, dir->caseFlags);
+    uint32_t fc = ((uint32_t)getLe16(dir->firstClusterHigh) << 16)
+                 | getLe16(dir->firstClusterLow);
+    pr->print(F("firstCluster: "));
+    pr->println(fc, HEX);
+    pr->print(F("fileSize: "));
+    pr->println(getLe32(dir->fileSize));
+  } else if (isFatLongName(dir)) {
+    pr->print(F("LFN: "));
+    for (uint8_t i = 0; i < 13; i++) {
+      uint16_t c = getLfnChar(ldir, i);
+      if (15 < c && c < 128) {
+        pr->print(static_cast<char>(c));
+      } else {
+        pr->print("0X");
+        pr->print(c, HEX);
+      }
+      pr->print(' ');
+    }
+    pr->println();
+    pr->print(F("order: 0X"));
+    pr->println(ldir->order, HEX);
+    pr->print(F("attributes: 0X"));
+    pr->println(ldir->attributes, HEX);
+    pr->print(F("checksum: 0X"));
+    pr->println(ldir->checksum, HEX);
+  } else {
+    pr->println(F("Other"));
+  }
+  pr->println();
+  return true;
+}
+//------------------------------------------------------------------------------
+void FatFile::dmpFile(print_t* pr, uint32_t pos, size_t n) {
+  char text[17];
+  text[16] = 0;
+  if (n >= 0XFFF0) {
+    n = 0XFFF0;
+  }
+  if (!seekSet(pos)) {
+    return;
+  }
+  for (size_t i = 0; i <= n; i++) {
+    if ((i & 15) == 0) {
+      if (i) {
+        pr->write(' ');
+        pr->write(text);
+        if (i == n) {
+          break;
+        }
+      }
+      pr->write('\r');
+      pr->write('\n');
+      if (i >= n) {
+        break;
+      }
+      printHex(pr, 4, i);
+      pr->write(' ');
+    }
+    int16_t h = read();
+    if (h < 0) {
+      break;
+    }
+    pr->write(' ');
+    printHex(pr, 2, h);
+    text[i&15] = ' ' <= h && h < 0X7F ? h : '.';
+  }
+  pr->write('\r');
+  pr->write('\n');
+}
+//------------------------------------------------------------------------------
+bool FatPartition::dmpDirSector(print_t* pr, uint32_t sector) {
+  DirFat_t dir[16];
+  if (!cacheSafeRead(sector, reinterpret_cast<uint8_t*>(dir))) {
+    pr->println(F("dmpDir failed"));
+    return false;
+  }
+  for (uint8_t i = 0; i < 16; i++) {
+    if (!printFatDir(pr, dir + i)) {
+      return false;
+    }
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+bool FatPartition::dmpRootDir(print_t* pr, uint32_t n) {
+  uint32_t sector;
+  if (fatType() == 16) {
+    sector = rootDirStart();
+  } else if (fatType() == 32) {
+    sector = clusterStartSector(rootDirStart());
+  } else {
+    pr->println(F("dmpRootDir failed"));
+    return false;
+  }
+  return dmpDirSector(pr, sector + n);
+}
+//------------------------------------------------------------------------------
+void FatPartition::dmpSector(print_t* pr, uint32_t sector, uint8_t bits) {
+  uint8_t data[FatPartition::m_bytesPerSector];
+  if (!cacheSafeRead(sector, data)) {
+    pr->println(F("dmpSector failed"));
+    return;
+  }
+  for (uint16_t i = 0; i < m_bytesPerSector;) {
+    if (i%32 == 0) {
+      if (i) {
+        pr->println();
+      }
+      printHex(pr, i);
+    }
+    pr->write(' ');
+    if (bits == 32) {
+      printHex(pr, *reinterpret_cast<uint32_t*>(data + i));
+      i += 4;
+    } else if (bits == 16) {
+      printHex(pr, *reinterpret_cast<uint16_t*>(data + i));
+      i += 2;
+    } else {
+      printHex(pr, data[i++]);
+    }
+  }
+  pr->println();
+}
+//------------------------------------------------------------------------------
+void FatPartition::dmpFat(print_t* pr, uint32_t start, uint32_t count) {
+  uint16_t nf = fatType() == 16 ? 256 : fatType() == 32 ? 128 : 0;
+  if (nf == 0) {
+    pr->println(F("Invalid fatType"));
+    return;
+  }
+  pr->println(F("FAT:"));
+  uint32_t sector = m_fatStartSector + start;
+  uint32_t cluster = nf*start;
+  for (uint32_t i = 0; i < count; i++) {
+    uint8_t* pc = fatCachePrepare(sector + i, FsCache::CACHE_FOR_READ);
+    if (!pc) {
+      pr->println(F("cache read failed"));
+      return;
+    }
+    for (size_t k = 0; k < nf; k++) {
+      if (0 == cluster%8) {
+        if (k) {
+          pr->println();
+        }
+        printHex(pr, cluster);
+      }
+      cluster++;
+      pr->write(' ');
+      uint32_t v = fatType() == 32 ? getLe32(pc + 4*k) : getLe16(pc + 2*k);
+      printHex(pr, v);
+    }
+    pr->println();
+  }
+}
+#endif  // DOXYGEN_SHOULD_SKIP_THIS

+ 1519 - 0
lib/SdFat_NoArduino/src/FatLib/FatFile.cpp

@@ -0,0 +1,1519 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "FatFile.cpp"
+#include "../common/DebugMacros.h"
+#include "FatLib.h"
+//------------------------------------------------------------------------------
+// Add a cluster to a file.
+bool FatFile::addCluster() {
+#if USE_FAT_FILE_FLAG_CONTIGUOUS
+  uint32_t cc = m_curCluster;
+  if (!m_vol->allocateCluster(m_curCluster, &m_curCluster)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (cc == 0) {
+    m_flags |= FILE_FLAG_CONTIGUOUS;
+  } else if (m_curCluster != (cc + 1)) {
+    m_flags &= ~FILE_FLAG_CONTIGUOUS;
+  }
+  m_flags |= FILE_FLAG_DIR_DIRTY;
+  return true;
+
+ fail:
+  return false;
+#else  // USE_FAT_FILE_FLAG_CONTIGUOUS
+  m_flags |= FILE_FLAG_DIR_DIRTY;
+  return m_vol->allocateCluster(m_curCluster, &m_curCluster);
+#endif  // USE_FAT_FILE_FLAG_CONTIGUOUS
+}
+//------------------------------------------------------------------------------
+// Add a cluster to a directory file and zero the cluster.
+// Return with first sector of cluster in the cache.
+bool FatFile::addDirCluster() {
+  uint32_t sector;
+  uint8_t* pc;
+
+  if (isRootFixed()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // max folder size
+  if (m_curPosition >= 512UL*4095) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (!addCluster()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  sector = m_vol->clusterStartSector(m_curCluster);
+  for (uint8_t i = 0; i < m_vol->sectorsPerCluster(); i++) {
+    pc = m_vol->dataCachePrepare(sector + i, FsCache::CACHE_RESERVE_FOR_WRITE);
+    if (!pc) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    memset(pc, 0, m_vol->bytesPerSector());
+  }
+  // Set position to EOF to avoid inconsistent curCluster/curPosition.
+  m_curPosition += m_vol->bytesPerCluster();
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::attrib(uint8_t bits) {
+  if (!isFileOrSubDir() || (bits & FS_ATTRIB_USER_SETTABLE) != bits) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Don't allow read-only to be set if the file is open for write.
+  if ((bits & FS_ATTRIB_READ_ONLY) && isWritable()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  m_attributes = (m_attributes & ~FS_ATTRIB_USER_SETTABLE) | bits;
+  // insure sync() will update dir entry
+  m_flags |= FILE_FLAG_DIR_DIRTY;
+  if (!sync()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+// cache a file's directory entry
+// return pointer to cached entry or null for failure
+DirFat_t* FatFile::cacheDirEntry(uint8_t action) {
+  uint8_t* pc = m_vol->dataCachePrepare(m_dirSector, action);
+  DirFat_t* dir = reinterpret_cast<DirFat_t*>(pc);
+  if (!dir) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return dir + (m_dirIndex & 0XF);
+
+ fail:
+  return nullptr;
+}
+//------------------------------------------------------------------------------
+bool FatFile::close() {
+  bool rtn = sync();
+  m_attributes = FILE_ATTR_CLOSED;
+  m_flags = 0;
+  return rtn;
+}
+//------------------------------------------------------------------------------
+bool FatFile::contiguousRange(uint32_t* bgnSector, uint32_t* endSector) {
+  // error if no clusters
+  if (!isFile() || m_firstCluster == 0) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  for (uint32_t c = m_firstCluster; ; c++) {
+    uint32_t next;
+    int8_t fg = m_vol->fatGet(c, &next);
+    if (fg < 0) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    // check for contiguous
+    if (fg == 0 || next != (c + 1)) {
+      // error if not end of chain
+      if (fg) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+#if USE_FAT_FILE_FLAG_CONTIGUOUS
+      m_flags |= FILE_FLAG_CONTIGUOUS;
+#endif  // USE_FAT_FILE_FLAG_CONTIGUOUS
+      if (bgnSector) {
+        *bgnSector = m_vol->clusterStartSector(m_firstCluster);
+      }
+      if (endSector) {
+        *endSector = m_vol->clusterStartSector(c)
+                     + m_vol->sectorsPerCluster() - 1;
+      }
+      return true;
+    }
+  }
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::createContiguous(const char* path, uint32_t size) {
+  if (!open(FatVolume::cwv(), path, O_CREAT | O_EXCL | O_RDWR)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (preAllocate(size)) {
+    return true;
+  }
+  close();
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::createContiguous(FatFile* dirFile,
+                               const char* path, uint32_t size) {
+  if (!open(dirFile, path, O_CREAT | O_EXCL | O_RDWR)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (preAllocate(size)) {
+    return true;
+  }
+  close();
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::dirEntry(DirFat_t* dst) {
+  DirFat_t* dir;
+  // Make sure fields on device are correct.
+  if (!sync()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // read entry
+  dir = cacheDirEntry(FsCache::CACHE_FOR_READ);
+  if (!dir) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // copy to caller's struct
+  memcpy(dst, dir, sizeof(DirFat_t));
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+uint32_t FatFile::dirSize() {
+  int8_t fg;
+  if (!isDir()) {
+    return 0;
+  }
+  if (isRootFixed()) {
+    return FS_DIR_SIZE*m_vol->rootDirEntryCount();
+  }
+  uint16_t n = 0;
+  uint32_t c = isRoot32() ? m_vol->rootDirStart() : m_firstCluster;
+  do {
+    fg = m_vol->fatGet(c, &c);
+    if (fg < 0 || n > 4095) {
+      return 0;
+    }
+    n += m_vol->sectorsPerCluster();
+  } while (fg);
+  return 512UL*n;
+}
+//------------------------------------------------------------------------------
+int FatFile::fgets(char* str, int num, char* delim) {
+  char ch;
+  int n = 0;
+  int r = -1;
+  while ((n + 1) < num && (r = read(&ch, 1)) == 1) {
+    // delete CR
+    if (ch == '\r') {
+      continue;
+    }
+    str[n++] = ch;
+    if (!delim) {
+      if (ch == '\n') {
+        break;
+      }
+    } else {
+      if (strchr(delim, ch)) {
+        break;
+      }
+    }
+  }
+  if (r < 0) {
+    // read error
+    return -1;
+  }
+  str[n] = '\0';
+  return n;
+}
+//------------------------------------------------------------------------------
+void FatFile::fgetpos(fspos_t* pos) const {
+  pos->position = m_curPosition;
+  pos->cluster = m_curCluster;
+}
+//------------------------------------------------------------------------------
+uint32_t FatFile::firstSector() const {
+  return m_firstCluster ? m_vol->clusterStartSector(m_firstCluster) : 0;
+}
+//------------------------------------------------------------------------------
+void FatFile::fsetpos(const fspos_t* pos) {
+  m_curPosition = pos->position;
+  m_curCluster = pos->cluster;
+}
+//------------------------------------------------------------------------------
+bool FatFile::getAccessDate(uint16_t* pdate) {
+  DirFat_t dir;
+  if (!dirEntry(&dir)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  *pdate = getLe16(dir.accessDate);
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::getCreateDateTime(uint16_t* pdate, uint16_t* ptime) {
+  DirFat_t dir;
+  if (!dirEntry(&dir)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  *pdate = getLe16(dir.createDate);
+  *ptime = getLe16(dir.createTime);
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::getModifyDateTime(uint16_t* pdate, uint16_t* ptime) {
+  DirFat_t dir;
+  if (!dirEntry(&dir)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  *pdate = getLe16(dir.modifyDate);
+  *ptime = getLe16(dir.modifyTime);
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::isBusy() {
+  return m_vol->isBusy();
+}
+//------------------------------------------------------------------------------
+bool FatFile::mkdir(FatFile* parent, const char* path, bool pFlag) {
+  FatName_t fname;
+  FatFile tmpDir;
+
+  if (isOpen() || !parent->isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (isDirSeparator(*path)) {
+    while (isDirSeparator(*path)) {
+      path++;
+    }
+    if (!tmpDir.openRoot(parent->m_vol)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    parent = &tmpDir;
+  }
+  while (1) {
+    if (!parsePathName(path, &fname, &path)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (!*path) {
+      break;
+    }
+    if (!open(parent, &fname, O_RDONLY)) {
+      if (!pFlag || !mkdir(parent, &fname)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+    tmpDir = *this;
+    parent = &tmpDir;
+    close();
+  }
+  return mkdir(parent, &fname);
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::mkdir(FatFile* parent, FatName_t* fname) {
+  uint32_t sector;
+  DirFat_t dot;
+  DirFat_t* dir;
+  uint8_t* pc;
+
+  if (!parent->isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // create a normal file
+  if (!open(parent, fname, O_CREAT | O_EXCL | O_RDWR)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // convert file to directory
+  m_flags = FILE_FLAG_READ;
+  m_attributes = FILE_ATTR_SUBDIR;
+
+  // allocate and zero first cluster
+  if (!addDirCluster()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  m_firstCluster = m_curCluster;
+  // Set to start of dir
+  rewind();
+  // force entry to device
+  if (!sync()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // cache entry - should already be in cache due to sync() call
+  dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE);
+  if (!dir) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // change directory entry attribute
+  dir->attributes = FS_ATTRIB_DIRECTORY;
+
+  // make entry for '.'
+  memcpy(&dot, dir, sizeof(dot));
+  dot.name[0] = '.';
+  for (uint8_t i = 1; i < 11; i++) {
+    dot.name[i] = ' ';
+  }
+
+  // cache sector for '.'  and '..'
+  sector = m_vol->clusterStartSector(m_firstCluster);
+  pc = m_vol->dataCachePrepare(sector, FsCache::CACHE_FOR_WRITE);
+  dir = reinterpret_cast<DirFat_t*>(pc);
+  if (!dir) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // copy '.' to sector
+  memcpy(&dir[0], &dot, sizeof(dot));
+  // make entry for '..'
+  dot.name[1] = '.';
+  setLe16(dot.firstClusterLow, parent->m_firstCluster & 0XFFFF);
+  setLe16(dot.firstClusterHigh, parent->m_firstCluster >> 16);
+  // copy '..' to sector
+  memcpy(&dir[1], &dot, sizeof(dot));
+  // write first sector
+  return m_vol->cacheSync();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::open(const char* path, oflag_t oflag) {
+  return open(FatVolume::cwv(), path, oflag);
+}
+//------------------------------------------------------------------------------
+bool FatFile::open(FatVolume* vol, const char* path, oflag_t oflag) {
+  return vol && open(vol->vwd(), path, oflag);
+}
+//------------------------------------------------------------------------------
+bool FatFile::open(FatFile* dirFile, const char* path, oflag_t oflag) {
+  FatFile tmpDir;
+  FatName_t fname;
+
+  // error if already open
+  if (isOpen() || !dirFile->isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (isDirSeparator(*path)) {
+    while (isDirSeparator(*path)) {
+      path++;
+    }
+    if (*path == 0) {
+      return openRoot(dirFile->m_vol);
+    }
+    if (!tmpDir.openRoot(dirFile->m_vol)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    dirFile = &tmpDir;
+  }
+  while (1) {
+    if (!parsePathName(path, &fname, &path)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (*path == 0) {
+      break;
+    }
+    if (!open(dirFile, &fname, O_RDONLY)) {
+      DBG_WARN_MACRO;
+      goto fail;
+    }
+    tmpDir = *this;
+    dirFile = &tmpDir;
+    close();
+  }
+  return open(dirFile, &fname, oflag);
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::open(uint16_t index, oflag_t oflag) {
+  FatVolume* vol = FatVolume::cwv();
+  return vol ? open(vol->vwd(), index, oflag) : false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::open(FatFile* dirFile, uint16_t index, oflag_t oflag) {
+  if (index) {
+    // Find start of LFN.
+    DirLfn_t* ldir;
+    uint8_t n = index < 20 ? index : 20;
+    for (uint8_t i = 1; i <= n; i++) {
+      ldir = reinterpret_cast<DirLfn_t*>(dirFile->cacheDir(index - i));
+      if (!ldir) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      if (ldir->attributes != FAT_ATTRIB_LONG_NAME) {
+        break;
+      }
+      if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) {
+        if (!dirFile->seekSet(32UL*(index - i))) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+        break;
+      }
+    }
+  } else {
+    dirFile->rewind();
+  }
+  if (!openNext(dirFile, oflag)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (dirIndex() != index) {
+    close();
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+// open a cached directory entry.
+bool FatFile::openCachedEntry(FatFile* dirFile, uint16_t dirIndex,
+                              oflag_t oflag, uint8_t lfnOrd) {
+  uint32_t firstCluster;
+  memset(this, 0, sizeof(FatFile));
+  // location of entry in cache
+  m_vol = dirFile->m_vol;
+  m_dirIndex = dirIndex;
+  m_dirCluster = dirFile->m_firstCluster;
+  DirFat_t* dir = reinterpret_cast<DirFat_t*>(m_vol->cacheAddress());
+  dir += 0XF & dirIndex;
+
+  // Must be file or subdirectory.
+  if (!isFatFileOrSubdir(dir)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  m_attributes = dir->attributes & FS_ATTRIB_COPY;
+  if (isFatFile(dir)) {
+    m_attributes |= FILE_ATTR_FILE;
+  }
+  m_lfnOrd = lfnOrd;
+
+  switch (oflag & O_ACCMODE) {
+    case O_RDONLY:
+      if (oflag & O_TRUNC) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      m_flags = FILE_FLAG_READ;
+      break;
+
+    case O_RDWR:
+      m_flags = FILE_FLAG_READ | FILE_FLAG_WRITE;
+      break;
+
+    case O_WRONLY:
+      m_flags = FILE_FLAG_WRITE;
+      break;
+
+    default:
+      DBG_FAIL_MACRO;
+      goto fail;
+  }
+
+  if (m_flags & FILE_FLAG_WRITE) {
+    if (isSubDir() || isReadOnly()) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    m_attributes |= FS_ATTRIB_ARCHIVE;
+  }
+  m_flags |= (oflag & O_APPEND ? FILE_FLAG_APPEND : 0);
+
+  m_dirSector = m_vol->cacheSectorNumber();
+
+  // copy first cluster number for directory fields
+  firstCluster = ((uint32_t)getLe16(dir->firstClusterHigh) << 16)
+                 | getLe16(dir->firstClusterLow);
+
+  if (oflag & O_TRUNC) {
+    if (firstCluster && !m_vol->freeChain(firstCluster)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    // need to update directory entry
+    m_flags |= FILE_FLAG_DIR_DIRTY;
+  } else {
+    m_firstCluster = firstCluster;
+    m_fileSize = getLe32(dir->fileSize);
+  }
+  if ((oflag & O_AT_END) && !seekSet(m_fileSize)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  m_attributes = FILE_ATTR_CLOSED;
+  m_flags = 0;
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::openCluster(FatFile* file) {
+  if (file->m_dirCluster == 0) {
+    return openRoot(file->m_vol);
+  }
+  memset(this, 0, sizeof(FatFile));
+  m_attributes = FILE_ATTR_SUBDIR;
+  m_flags = FILE_FLAG_READ;
+  m_vol = file->m_vol;
+  m_firstCluster = file->m_dirCluster;
+  return true;
+}
+//------------------------------------------------------------------------------
+bool FatFile::openCwd() {
+  if (isOpen() || !FatVolume::cwv()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  *this = *FatVolume::cwv()->vwd();
+  rewind();
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::openNext(FatFile* dirFile, oflag_t oflag) {
+  uint8_t checksum = 0;
+  DirLfn_t* ldir;
+  uint8_t lfnOrd = 0;
+  uint16_t index;
+
+  // Check for not open and valid directory..
+  if (isOpen() || !dirFile->isDir() || (dirFile->curPosition() & 0X1F)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  while (1) {
+    // read entry into cache
+    index = dirFile->curPosition()/FS_DIR_SIZE;
+    DirFat_t* dir = dirFile->readDirCache();
+    if (!dir) {
+      if (dirFile->getError()) {
+        DBG_FAIL_MACRO;
+      }
+      goto fail;
+    }
+    // done if last entry
+    if (dir->name[0] == FAT_NAME_FREE) {
+      goto fail;
+    }
+    // skip empty slot or '.' or '..'
+    if (dir->name[0] == '.' || dir->name[0] == FAT_NAME_DELETED) {
+      lfnOrd = 0;
+    } else if (isFatFileOrSubdir(dir)) {
+      if (lfnOrd && checksum != lfnChecksum(dir->name)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      if (!openCachedEntry(dirFile, index, oflag, lfnOrd)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      return true;
+    } else if (isFatLongName(dir)) {
+      ldir = reinterpret_cast<DirLfn_t*>(dir);
+      if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) {
+        lfnOrd = ldir->order & 0X1F;
+        checksum = ldir->checksum;
+      }
+    } else {
+      lfnOrd = 0;
+    }
+  }
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::openRoot(FatVolume* vol) {
+  // error if file is already open
+  if (isOpen()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  memset(this, 0, sizeof(FatFile));
+
+  m_vol = vol;
+  switch (vol->fatType()) {
+#if FAT12_SUPPORT
+  case 12:
+#endif  // FAT12_SUPPORT
+  case 16:
+    m_attributes = FILE_ATTR_ROOT_FIXED;
+    break;
+
+  case 32:
+    m_attributes = FILE_ATTR_ROOT32;
+    break;
+
+  default:
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // read only
+  m_flags = FILE_FLAG_READ;
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+int FatFile::peek() {
+  uint32_t curPosition = m_curPosition;
+  uint32_t curCluster = m_curCluster;
+  int c = read();
+  m_curPosition = curPosition;
+  m_curCluster = curCluster;
+  return c;
+}
+//------------------------------------------------------------------------------
+bool FatFile::preAllocate(uint32_t length) {
+  uint32_t need;
+  if (!length || !isWritable() || m_firstCluster) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  need = 1 + ((length - 1) >> m_vol->bytesPerClusterShift());
+  // allocate clusters
+  if (!m_vol->allocContiguous(need, &m_firstCluster)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  m_fileSize = length;
+
+#if USE_FAT_FILE_FLAG_CONTIGUOUS
+  // Mark contiguous and insure sync() will update dir entry
+  m_flags |= FILE_FLAG_PREALLOCATE | FILE_FLAG_CONTIGUOUS | FILE_FLAG_DIR_DIRTY;
+#else  // USE_FAT_FILE_FLAG_CONTIGUOUS
+  // insure sync() will update dir entry
+  m_flags |= FILE_FLAG_DIR_DIRTY;
+#endif  // USE_FAT_FILE_FLAG_CONTIGUOUS
+  return sync();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+int FatFile::read(void* buf, size_t nbyte) {
+  int8_t fg;
+  uint8_t sectorOfCluster = 0;
+  uint8_t* dst = reinterpret_cast<uint8_t*>(buf);
+  uint16_t offset;
+  size_t toRead;
+  uint32_t sector;  // raw device sector number
+  uint8_t* pc;
+  // error if not open for read
+  if (!isReadable()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+
+  if (isFile()) {
+    uint32_t tmp32 = m_fileSize - m_curPosition;
+    if (nbyte >= tmp32) {
+      nbyte = tmp32;
+    }
+  } else if (isRootFixed()) {
+    uint16_t tmp16 =
+      FS_DIR_SIZE*m_vol->m_rootDirEntryCount - (uint16_t)m_curPosition;
+    if (nbyte > tmp16) {
+      nbyte = tmp16;
+    }
+  }
+  toRead = nbyte;
+  while (toRead) {
+    size_t n;
+    offset = m_curPosition & m_vol->sectorMask();  // offset in sector
+    if (isRootFixed()) {
+      sector = m_vol->rootDirStart()
+               + (m_curPosition >> m_vol->bytesPerSectorShift());
+    } else {
+      sectorOfCluster = m_vol->sectorOfCluster(m_curPosition);
+      if (offset == 0 && sectorOfCluster == 0) {
+        // start of new cluster
+        if (m_curPosition == 0) {
+          // use first cluster in file
+          m_curCluster = isRoot32() ? m_vol->rootDirStart() : m_firstCluster;
+#if USE_FAT_FILE_FLAG_CONTIGUOUS
+        } else if (isFile() && isContiguous()) {
+          m_curCluster++;
+#endif  // USE_FAT_FILE_FLAG_CONTIGUOUS
+        } else {
+          // get next cluster from FAT
+          fg = m_vol->fatGet(m_curCluster, &m_curCluster);
+          if (fg < 0) {
+            DBG_FAIL_MACRO;
+            goto fail;
+          }
+          if (fg == 0) {
+            if (isDir()) {
+              break;
+            }
+            DBG_FAIL_MACRO;
+            goto fail;
+          }
+        }
+      }
+      sector = m_vol->clusterStartSector(m_curCluster) + sectorOfCluster;
+    }
+    if (offset != 0 || toRead < m_vol->bytesPerSector()
+        || sector == m_vol->cacheSectorNumber()) {
+      // amount to be read from current sector
+      n = m_vol->bytesPerSector() - offset;
+      if (n > toRead) {
+        n = toRead;
+      }
+      // read sector to cache and copy data to caller
+      pc = m_vol->dataCachePrepare(sector, FsCache::CACHE_FOR_READ);
+      if (!pc) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      uint8_t* src = pc + offset;
+      memcpy(dst, src, n);
+#if USE_MULTI_SECTOR_IO
+    } else if (toRead >= 2*m_vol->bytesPerSector()) {
+      uint32_t ns = toRead >> m_vol->bytesPerSectorShift();
+      if (!isRootFixed()) {
+        uint32_t mb = m_vol->sectorsPerCluster() - sectorOfCluster;
+        if (mb < ns) {
+          ns = mb;
+        }
+      }
+      n = ns << m_vol->bytesPerSectorShift();
+      if (!m_vol->cacheSafeRead(sector, dst, ns)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+#endif  // USE_MULTI_SECTOR_IO
+    } else {
+      // read single sector
+      n = m_vol->bytesPerSector();
+      if (!m_vol->cacheSafeRead(sector, dst)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+    dst += n;
+    m_curPosition += n;
+    toRead -= n;
+  }
+  return nbyte - toRead;
+
+ fail:
+  m_error |= READ_ERROR;
+  return -1;
+}
+//------------------------------------------------------------------------------
+int8_t FatFile::readDir(DirFat_t* dir) {
+  int16_t n;
+  // if not a directory file or miss-positioned return an error
+  if (!isDir() || (0X1F & m_curPosition)) {
+    return -1;
+  }
+
+  while (1) {
+    n = read(dir, sizeof(DirFat_t));
+    if (n != sizeof(DirFat_t)) {
+      return n == 0 ? 0 : -1;
+    }
+    // last entry if FAT_NAME_FREE
+    if (dir->name[0] == FAT_NAME_FREE) {
+      return 0;
+    }
+    // skip empty entries and entry for .  and ..
+    if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') {
+      continue;
+    }
+    // return if normal file or subdirectory
+    if (isFatFileOrSubdir(dir)) {
+      return n;
+    }
+  }
+}
+//------------------------------------------------------------------------------
+// Read next directory entry into the cache.
+// Assumes file is correctly positioned.
+DirFat_t* FatFile::readDirCache(bool skipReadOk) {
+  DBG_HALT_IF(m_curPosition & 0X1F);
+  uint8_t i = (m_curPosition >> 5) & 0XF;
+
+  if (i == 0 || !skipReadOk) {
+    int8_t n = read(&n, 1);
+    if  (n != 1) {
+      if (n != 0) {
+        DBG_FAIL_MACRO;
+      }
+      goto fail;
+    }
+    m_curPosition += FS_DIR_SIZE - 1;
+  } else {
+    m_curPosition += FS_DIR_SIZE;
+  }
+  // return pointer to entry
+  return reinterpret_cast<DirFat_t*>(m_vol->cacheAddress()) + i;
+
+ fail:
+  return nullptr;
+}
+//------------------------------------------------------------------------------
+bool FatFile::remove(const char* path) {
+  FatFile file;
+  if (!file.open(this, path, O_WRONLY)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return file.remove();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::rename(const char* newPath) {
+  return rename(m_vol->vwd(), newPath);
+}
+//------------------------------------------------------------------------------
+bool FatFile::rename(FatFile* dirFile, const char* newPath) {
+  DirFat_t entry;
+  uint32_t dirCluster = 0;
+  FatFile file;
+  FatFile oldFile;
+  uint8_t* pc;
+  DirFat_t* dir;
+
+  // Must be an open file or subdirectory.
+  if (!(isFile() || isSubDir())) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Can't rename LFN in 8.3 mode.
+  if (!USE_LONG_FILE_NAMES && isLFN()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Can't move file to new volume.
+  if (m_vol != dirFile->m_vol) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // sync() and cache directory entry
+  sync();
+  oldFile = *this;
+  dir = cacheDirEntry(FsCache::CACHE_FOR_READ);
+  if (!dir) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // save directory entry
+  memcpy(&entry, dir, sizeof(entry));
+  // make directory entry for new path
+  if (isFile()) {
+    if (!file.open(dirFile, newPath, O_CREAT | O_EXCL | O_WRONLY)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  } else {
+    // don't create missing path prefix components
+    if (!file.mkdir(dirFile, newPath, false)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    // save cluster containing new dot dot
+    dirCluster = file.m_firstCluster;
+  }
+  // change to new directory entry
+
+  m_dirSector = file.m_dirSector;
+  m_dirIndex = file.m_dirIndex;
+  m_lfnOrd = file.m_lfnOrd;
+  m_dirCluster = file.m_dirCluster;
+  // mark closed to avoid possible destructor close call
+  file.m_attributes = FILE_ATTR_CLOSED;
+  file.m_flags = 0;
+
+  // cache new directory entry
+  dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE);
+  if (!dir) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // copy all but name and name flags to new directory entry
+  memcpy(&dir->createTimeMs, &entry.createTimeMs,
+         sizeof(entry) - sizeof(dir->name) - 2);
+  dir->attributes = entry.attributes;
+
+  // update dot dot if directory
+  if (dirCluster) {
+    // get new dot dot
+    uint32_t sector = m_vol->clusterStartSector(dirCluster);
+    pc = m_vol->dataCachePrepare(sector, FsCache::CACHE_FOR_READ);
+    dir = reinterpret_cast<DirFat_t*>(pc);
+    if (!dir) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    memcpy(&entry, &dir[1], sizeof(entry));
+
+    // free unused cluster
+    if (!m_vol->freeChain(dirCluster)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    // store new dot dot
+    sector = m_vol->clusterStartSector(m_firstCluster);
+    uint8_t* pc = m_vol->dataCachePrepare(sector, FsCache::CACHE_FOR_WRITE);
+    dir = reinterpret_cast<DirFat_t*>(pc);
+    if (!dir) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    memcpy(&dir[1], &entry, sizeof(entry));
+  }
+  // Remove old directory entry;
+  oldFile.m_firstCluster = 0;
+  oldFile.m_flags = FILE_FLAG_WRITE;
+  oldFile.m_attributes = FILE_ATTR_FILE;
+  if (!oldFile.remove()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return m_vol->cacheSync();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::rmdir() {
+  // must be open subdirectory
+  if (!isSubDir() || (!USE_LONG_FILE_NAMES && isLFN())) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  rewind();
+
+  // make sure directory is empty
+  while (1) {
+    DirFat_t* dir = readDirCache(true);
+    if (!dir) {
+      // EOF if no error.
+      if (!getError()) {
+        break;
+      }
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    // done if past last used entry
+    if (dir->name[0] == FAT_NAME_FREE) {
+      break;
+    }
+    // skip empty slot, '.' or '..'
+    if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') {
+      continue;
+    }
+    // error not empty
+    if (isFatFileOrSubdir(dir)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+  // convert empty directory to normal file for remove
+  m_attributes = FILE_ATTR_FILE;
+  m_flags |= FILE_FLAG_WRITE;
+  return remove();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::rmRfStar() {
+  uint16_t index;
+  FatFile f;
+  if (!isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  rewind();
+  while (1) {
+    // remember position
+    index = m_curPosition/FS_DIR_SIZE;
+
+    DirFat_t* dir = readDirCache();
+    if (!dir) {
+      // At EOF if no error.
+      if (!getError()) {
+        break;
+      }
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    // done if past last entry
+    if (dir->name[0] == FAT_NAME_FREE) {
+      break;
+    }
+
+    // skip empty slot or '.' or '..'
+    if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') {
+      continue;
+    }
+
+    // skip if part of long file name or volume label in root
+    if (!isFatFileOrSubdir(dir)) {
+      continue;
+    }
+
+    if (!f.open(this, index, O_RDONLY)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (f.isSubDir()) {
+      // recursively delete
+      if (!f.rmRfStar()) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    } else {
+      // ignore read-only
+      f.m_flags |= FILE_FLAG_WRITE;
+      if (!f.remove()) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+    // position to next entry if required
+    if (m_curPosition != (32UL*(index + 1))) {
+      if (!seekSet(32UL*(index + 1))) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+  }
+  // don't try to delete root
+  if (!isRoot()) {
+    if (!rmdir()) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::seekSet(uint32_t pos) {
+  uint32_t nCur;
+  uint32_t nNew;
+  uint32_t tmp = m_curCluster;
+  // error if file not open
+  if (!isOpen()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Optimize O_APPEND writes.
+  if (pos == m_curPosition) {
+    return true;
+  }
+  if (pos == 0) {
+    // set position to start of file
+    m_curCluster = 0;
+    goto done;
+  }
+  if (isFile()) {
+    if (pos > m_fileSize) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  } else if (isRootFixed()) {
+    if (pos <= FS_DIR_SIZE*m_vol->rootDirEntryCount()) {
+      goto done;
+    }
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // calculate cluster index for new position
+  nNew = (pos - 1) >> (m_vol->bytesPerClusterShift());
+#if USE_FAT_FILE_FLAG_CONTIGUOUS
+  if (isContiguous()) {
+    m_curCluster = m_firstCluster + nNew;
+    goto done;
+  }
+#endif  // USE_FAT_FILE_FLAG_CONTIGUOUS
+  // calculate cluster index for current position
+  nCur = (m_curPosition - 1) >> (m_vol->bytesPerClusterShift());
+
+  if (nNew < nCur || m_curPosition == 0) {
+    // must follow chain from first cluster
+    m_curCluster = isRoot32() ? m_vol->rootDirStart() : m_firstCluster;
+  } else {
+    // advance from curPosition
+    nNew -= nCur;
+  }
+  while (nNew--) {
+    if (m_vol->fatGet(m_curCluster, &m_curCluster) <= 0) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+
+ done:
+  m_curPosition = pos;
+  m_flags &= ~FILE_FLAG_PREALLOCATE;
+  return true;
+
+ fail:
+  m_curCluster = tmp;
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::sync() {
+  uint16_t date, time;
+  uint8_t ms10;
+  if (!isOpen()) {
+    return true;
+  }
+  if (m_flags & FILE_FLAG_DIR_DIRTY) {
+    DirFat_t* dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE);
+    // check for deleted by another open file object
+    if (!dir || dir->name[0] == FAT_NAME_DELETED) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    dir->attributes = m_attributes & FS_ATTRIB_COPY;
+    // do not set filesize for dir files
+    if (isFile()) {
+      setLe32(dir->fileSize, m_fileSize);
+    }
+    // update first cluster fields
+    setLe16(dir->firstClusterLow, m_firstCluster & 0XFFFF);
+    setLe16(dir->firstClusterHigh, m_firstCluster >> 16);
+
+    // set modify time if user supplied a callback date/time function
+    if (FsDateTime::callback) {
+      FsDateTime::callback(&date, &time, &ms10);
+      setLe16(dir->modifyDate, date);
+      setLe16(dir->accessDate, date);
+      setLe16(dir->modifyTime, time);
+    }
+    // clear directory dirty
+    m_flags &= ~FILE_FLAG_DIR_DIRTY;
+  }
+  if (m_vol->cacheSync()) {
+    return true;
+  }
+  DBG_FAIL_MACRO;
+
+ fail:
+  m_error |= WRITE_ERROR;
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::timestamp(uint8_t flags, uint16_t year, uint8_t month,
+                   uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
+  uint16_t dirDate;
+  uint16_t dirTime;
+  DirFat_t* dir;
+
+  if (!isFile()
+      || year < 1980
+      || year > 2107
+      || month < 1
+      || month > 12
+      || day < 1
+      || day > 31
+      || hour > 23
+      || minute > 59
+      || second > 59) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // update directory entry
+  if (!sync()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE);
+  if (!dir) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  dirDate = FS_DATE(year, month, day);
+  dirTime = FS_TIME(hour, minute, second);
+  if (flags & T_ACCESS) {
+    setLe16(dir->accessDate, dirDate);
+  }
+  if (flags & T_CREATE) {
+    setLe16(dir->createDate, dirDate);
+    setLe16(dir->createTime, dirTime);
+    // units of 10 ms
+    dir->createTimeMs = second & 1 ? 100 : 0;
+  }
+  if (flags & T_WRITE) {
+    setLe16(dir->modifyDate, dirDate);
+    setLe16(dir->modifyTime, dirTime);
+  }
+  return m_vol->cacheSync();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::truncate() {
+  uint32_t toFree;
+  // error if not a normal file or read-only
+  if (!isWritable()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (m_firstCluster == 0) {
+      return true;
+  }
+  if (m_curCluster) {
+    toFree = 0;
+    int8_t fg = m_vol->fatGet(m_curCluster, &toFree);
+    if (fg < 0) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (fg) {
+      // current cluster is end of chain
+      if (!m_vol->fatPutEOC(m_curCluster)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+  } else {
+    toFree = m_firstCluster;
+    m_firstCluster = 0;
+  }
+  if (toFree) {
+    if (!m_vol->freeChain(toFree)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+  m_fileSize = m_curPosition;
+
+  // need to update directory entry
+  m_flags |= FILE_FLAG_DIR_DIRTY;
+  return sync();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+size_t FatFile::write(const void* buf, size_t nbyte) {
+  // convert void* to uint8_t*  -  must be before goto statements
+  const uint8_t* src = reinterpret_cast<const uint8_t*>(buf);
+  uint8_t* pc;
+  uint8_t cacheOption;
+  // number of bytes left to write  -  must be before goto statements
+  size_t nToWrite = nbyte;
+  size_t n;
+  // error if not a normal file or is read-only
+  if (!isWritable()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // seek to end of file if append flag
+  if ((m_flags & FILE_FLAG_APPEND)) {
+    if (!seekSet(m_fileSize)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+  // Don't exceed max fileSize.
+  if (nbyte > (0XFFFFFFFF - m_curPosition)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  while (nToWrite) {
+    uint8_t sectorOfCluster = m_vol->sectorOfCluster(m_curPosition);
+    uint16_t sectorOffset = m_curPosition & m_vol->sectorMask();
+    if (sectorOfCluster == 0 && sectorOffset == 0) {
+      // start of new cluster
+      if (m_curCluster != 0) {
+#if USE_FAT_FILE_FLAG_CONTIGUOUS
+        int8_t fg;
+        if (isContiguous() && m_fileSize > m_curPosition) {
+          m_curCluster++;
+          fg = 1;
+        } else {
+          fg = m_vol->fatGet(m_curCluster, &m_curCluster);
+          if (fg < 0) {
+            DBG_FAIL_MACRO;
+            goto fail;
+          }
+        }
+#else  // USE_FAT_FILE_FLAG_CONTIGUOUS
+        int8_t fg = m_vol->fatGet(m_curCluster, &m_curCluster);
+        if (fg < 0) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+#endif  // USE_FAT_FILE_FLAG_CONTIGUOUS
+        if (fg == 0) {
+          // add cluster if at end of chain
+          if (!addCluster()) {
+            DBG_FAIL_MACRO;
+            goto fail;
+          }
+        }
+      } else {
+        if (m_firstCluster == 0) {
+          // allocate first cluster of file
+          if (!addCluster()) {
+            DBG_FAIL_MACRO;
+            goto fail;
+          }
+          m_firstCluster = m_curCluster;
+        } else {
+          m_curCluster = m_firstCluster;
+        }
+      }
+    }
+    // sector for data write
+    uint32_t sector = m_vol->clusterStartSector(m_curCluster)
+                      + sectorOfCluster;
+
+    if (sectorOffset != 0 || nToWrite < m_vol->bytesPerSector()) {
+      // partial sector - must use cache
+      // max space in sector
+      n = m_vol->bytesPerSector() - sectorOffset;
+      // lesser of space and amount to write
+      if (n > nToWrite) {
+        n = nToWrite;
+      }
+
+      if (sectorOffset == 0 &&
+         (m_curPosition >= m_fileSize || m_flags & FILE_FLAG_PREALLOCATE)) {
+        // start of new sector don't need to read into cache
+        cacheOption = FsCache::CACHE_RESERVE_FOR_WRITE;
+      } else {
+        // rewrite part of sector
+        cacheOption = FsCache::CACHE_FOR_WRITE;
+      }
+      pc = m_vol->dataCachePrepare(sector, cacheOption);
+      if (!pc) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      uint8_t* dst = pc + sectorOffset;
+      memcpy(dst, src, n);
+      if (m_vol->bytesPerSector() == (n + sectorOffset)) {
+        // Force write if sector is full - improves large writes.
+        if (!m_vol->cacheSyncData()) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+      }
+#if USE_MULTI_SECTOR_IO
+    } else if (nToWrite >= 2*m_vol->bytesPerSector()) {
+      // use multiple sector write command
+      uint32_t maxSectors = m_vol->sectorsPerCluster() - sectorOfCluster;
+      uint32_t nSector = nToWrite >> m_vol->bytesPerSectorShift();
+      if (nSector > maxSectors) {
+        nSector = maxSectors;
+      }
+      n = nSector << m_vol->bytesPerSectorShift();
+      if (!m_vol->cacheSafeWrite(sector, src, nSector)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+#endif  // USE_MULTI_SECTOR_IO
+    } else {
+      // use single sector write command
+      n = m_vol->bytesPerSector();
+      if (!m_vol->cacheSafeWrite(sector, src)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+    m_curPosition += n;
+    src += n;
+    nToWrite -= n;
+  }
+  if (m_curPosition > m_fileSize) {
+    // update fileSize and insure sync will update dir entry
+    m_fileSize = m_curPosition;
+    m_flags |= FILE_FLAG_DIR_DIRTY;
+  } else if (FsDateTime::callback) {
+    // insure sync will update modified date and time
+    m_flags |= FILE_FLAG_DIR_DIRTY;
+  }
+  return nbyte;
+
+ fail:
+  // return for write error
+  m_error |= WRITE_ERROR;
+  return 0;
+}

+ 1086 - 0
lib/SdFat_NoArduino/src/FatLib/FatFile.h

@@ -0,0 +1,1086 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FatFile_h
+#define FatFile_h
+/**
+ * \file
+ * \brief FatFile class
+ */
+#include <string.h>
+#include <stddef.h>
+#include <limits.h>
+#include "../common/FmtNumber.h"
+#include "../common/FsApiConstants.h"
+#include "../common/FsDateTime.h"
+#include "../common/FsName.h"
+#include "FatPartition.h"
+class FatVolume;
+//------------------------------------------------------------------------------
+// Stuff to store strings in AVR flash.
+#ifdef __AVR__
+#include <avr/pgmspace.h>
+#else  // __AVR__
+#ifndef PSTR
+/** store literal string in flash for ARM */
+#define PSTR(x) (x)
+#endif  // PSTR
+#ifndef pgm_read_byte
+/** read 8-bits from flash for ARM */
+#define pgm_read_byte(addr) (*(const unsigned char*)(addr))
+#endif  // pgm_read_byte
+#ifndef pgm_read_word
+/** read 16-bits from flash for ARM */
+#define pgm_read_word(addr) (*(const uint16_t*)(addr))
+#endif  // pgm_read_word
+#ifndef PROGMEM
+/** store in flash for ARM */
+#define PROGMEM
+#endif  // PROGMEM
+#endif  // __AVR__
+//------------------------------------------------------------------------------
+/**
+ * \struct FatPos_t
+ * \brief Internal type for file position - do not use in user apps.
+ */
+struct FatPos_t {
+  /** stream position */
+  uint32_t position;
+  /** cluster for position */
+  uint32_t cluster;
+};
+//------------------------------------------------------------------------------
+/** Expression for path name separator. */
+#define isDirSeparator(c) ((c) == '/')
+//------------------------------------------------------------------------------
+/**
+ * \class FatLfn_t
+ * \brief Internal type for Long File Name - do not use in user apps.
+ */
+
+class FatLfn_t : public FsName {
+ public:
+  /** UTF-16 length of Long File Name */
+  size_t len;
+  /** Position for sequence number. */
+  uint8_t seqPos;
+  /** Flags for base and extension character case and LFN. */
+  uint8_t flags;
+  /** Short File Name */
+  uint8_t sfn[11];
+};
+/**
+ * \class FatSfn_t
+ * \brief Internal type for Short 8.3 File Name - do not use in user apps.
+ */
+class FatSfn_t {
+ public:
+  /** Flags for base and extension character case and LFN. */
+  uint8_t flags;
+  /** Short File Name */
+  uint8_t sfn[11];
+};
+
+#if USE_LONG_FILE_NAMES
+/** Internal class for file names */
+typedef FatLfn_t FatName_t;
+#else  // USE_LONG_FILE_NAMES
+/** Internal class for file names */
+typedef FatSfn_t FatName_t;
+#endif  // USE_LONG_FILE_NAMES
+
+/** Derived from a LFN with loss or conversion of characters. */
+const uint8_t FNAME_FLAG_LOST_CHARS = 0X01;
+/** Base-name or extension has mixed case. */
+const uint8_t FNAME_FLAG_MIXED_CASE = 0X02;
+/** LFN entries are required for file name. */
+const uint8_t FNAME_FLAG_NEED_LFN =
+  FNAME_FLAG_LOST_CHARS | FNAME_FLAG_MIXED_CASE;
+/** Filename base-name is all lower case */
+const uint8_t FNAME_FLAG_LC_BASE = FAT_CASE_LC_BASE;
+/** Filename extension is all lower case. */
+const uint8_t FNAME_FLAG_LC_EXT = FAT_CASE_LC_EXT;
+#if FNAME_FLAG_NEED_LFN & (FAT_CASE_LC_BASE || FAT_CASE_LC_EXT)
+#error FNAME_FLAG_NEED_LFN & (FAT_CASE_LC_BASE || FAT_CASE_LC_EXT)
+#endif  // FNAME_FLAG_NEED_LFN & (FAT_CASE_LC_BASE || FAT_CASE_LC_EXT)
+//==============================================================================
+/**
+ * \class FatFile
+ * \brief Basic file class.
+ */
+class FatFile {
+ public:
+  /** Create an instance. */
+  FatFile() {}
+  /**  Create a file object and open it in the current working directory.
+   *
+   * \param[in] path A path for a file to be opened.
+   *
+   * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
+   * OR of open flags. see FatFile::open(FatFile*, const char*, uint8_t).
+   */
+  FatFile(const char* path, oflag_t oflag) {
+    open(path, oflag);
+  }
+#if DESTRUCTOR_CLOSES_FILE
+  /** Destructor */
+  ~FatFile() {
+    if (isOpen()) {
+      close();
+    }
+  }
+#endif  // DESTRUCTOR_CLOSES_FILE
+  /** The parenthesis operator.
+   *
+   * \return true if a file is open.
+   */
+  operator bool() const {return isOpen();}
+  /**
+   * \return user settable file attributes for success else -1.
+   */
+  int attrib() {
+    return isFileOrSubDir() ? m_attributes & FS_ATTRIB_USER_SETTABLE : -1;
+  }
+  /** Set file attributes
+   *
+   * \param[in] bits bit-wise or of selected attributes: FS_ATTRIB_READ_ONLY,
+   *            FS_ATTRIB_HIDDEN, FS_ATTRIB_SYSTEM, FS_ATTRIB_ARCHIVE.
+   *
+   * \note attrib() will fail for set read-only if the file is open for write.
+   * \return true for success or false for failure.
+   */
+  bool attrib(uint8_t bits);
+  /** \return The number of bytes available from the current position
+   * to EOF for normal files.  INT_MAX is returned for very large files.
+   *
+   * available32() is recomended for very large files.
+   *
+   * Zero is returned for directory files.
+   *
+   */
+  int available() const {
+    uint32_t n = available32();
+    return n > INT_MAX ? INT_MAX : n;
+  }
+  /** \return The number of bytes available from the current position
+   * to EOF for normal files.  Zero is returned for directory files.
+   */
+  uint32_t available32() const {
+    return isFile() ? fileSize() - curPosition() : 0;
+  }
+  /** Clear all error bits. */
+  void clearError() {
+    m_error = 0;
+  }
+  /** Set writeError to zero */
+  void clearWriteError() {
+    m_error &= ~WRITE_ERROR;
+  }
+  /** Close a file and force cached data and directory information
+   *  to be written to the storage device.
+   *
+   * \return true for success or false for failure.
+   */
+  bool close();
+  /** Check for contiguous file and return its raw sector range.
+   *
+   * \param[out] bgnSector the first sector address for the file.
+   * \param[out] endSector the last sector address for the file.
+   *
+   * Set the contiguous flag if the file is contiguous.
+   * The parameters may be nullptr to only set the flag.
+   * \return true for success or false for failure.
+   */
+  bool contiguousRange(uint32_t* bgnSector, uint32_t* endSector);
+  /** Create and open a new contiguous file of a specified size.
+   *
+   * \param[in] dirFile The directory where the file will be created.
+   * \param[in] path A path with a valid file name.
+   * \param[in] size The desired file size.
+   *
+   * \return true for success or false for failure.
+   */
+  bool createContiguous(FatFile* dirFile,
+                        const char* path, uint32_t size);
+  /** Create and open a new contiguous file of a specified size.
+   *
+   * \param[in] path A path with a valid file name.
+   * \param[in] size The desired file size.
+   *
+   * \return true for success or false for failure.
+   */
+  bool createContiguous(const char* path, uint32_t size);
+  /** \return The current cluster number for a file or directory. */
+  uint32_t curCluster() const {return m_curCluster;}
+
+  /** \return The current position for a file or directory. */
+  uint32_t curPosition() const {return m_curPosition;}
+  /** Return a file's directory entry.
+   *
+   * \param[out] dir Location for return of the file's directory entry.
+   *
+   * \return true for success or false for failure.
+   */
+  bool dirEntry(DirFat_t* dir);
+  /** \return Directory entry index. */
+  uint16_t dirIndex() const {return m_dirIndex;}
+  /** \return The number of bytes allocated to a directory or zero
+   *         if an error occurs.
+   */
+  uint32_t dirSize();
+  /** Dump file in Hex
+   * \param[in] pr Print stream for list.
+   * \param[in] pos Start position in file.
+   * \param[in] n number of locations to dump.
+   */
+  void dmpFile(print_t* pr, uint32_t pos, size_t n);
+  /** Test for the existence of a file in a directory
+   *
+   * \param[in] path Path of the file to be tested for.
+   *
+   * The calling instance must be an open directory file.
+   *
+   * dirFile.exists("TOFIND.TXT") searches for "TOFIND.TXT" in the directory
+   * dirFile.
+   *
+   * \return True if the file exists.
+   */
+  bool exists(const char* path) {
+    FatFile file;
+    return file.open(this, path, O_RDONLY);
+  }
+  /** get position for streams
+   * \param[out] pos struct to receive position
+   */
+  void fgetpos(fspos_t* pos) const;
+  /**
+   * Get a string from a file.
+   *
+   * fgets() reads bytes from a file into the array pointed to by \a str, until
+   * \a num - 1 bytes are read, or a delimiter is read and transferred to
+   * \a str, or end-of-file is encountered. The string is then terminated
+   * with a null byte.
+   *
+   * fgets() deletes CR, '\\r', from the string.  This insures only a '\\n'
+   * terminates the string for Windows text files which use CRLF for newline.
+   *
+   * \param[out] str Pointer to the array where the string is stored.
+   * \param[in] num Maximum number of characters to be read
+   * (including the final null byte). Usually the length
+   * of the array \a str is used.
+   * \param[in] delim Optional set of delimiters. The default is "\n".
+   *
+   * \return For success fgets() returns the length of the string in \a str.
+   * If no data is read, fgets() returns zero for EOF or -1 if an error
+   * occurred.
+   */
+  int fgets(char* str, int num, char* delim = nullptr);
+  /** \return The total number of bytes in a file. */
+  uint32_t fileSize() const {return m_fileSize;}
+  /** \return first sector of file or zero for empty file. */
+  uint32_t firstBlock() const {return firstSector();}
+  /** \return Address of first sector or zero for empty file. */
+  uint32_t firstSector() const;
+  /** Arduino name for sync() */
+  void flush() {sync();}
+  /** set position for streams
+   * \param[in] pos struct with value for new position
+   */
+  void fsetpos(const fspos_t* pos);
+  /** Get a file's access date.
+   *
+   * \param[out] pdate Packed date for directory entry.
+   *
+   * \return true for success or false for failure.
+   */
+  bool getAccessDate(uint16_t* pdate);
+  /** Get a file's access date and time.
+   *
+   * \param[out] pdate Packed date for directory entry.
+   * \param[out] ptime return zero since FAT has no time.
+   *
+   * This function is for comparability in FsFile.
+   *
+   * \return true for success or false for failure.
+   */
+  bool getAccessDateTime(uint16_t* pdate, uint16_t* ptime) {
+    if (!getAccessDate(pdate)) {
+      return false;
+    }
+    *ptime = 0;
+    return true;
+  }
+  /** Get a file's create date and time.
+   *
+   * \param[out] pdate Packed date for directory entry.
+   * \param[out] ptime Packed time for directory entry.
+   *
+   * \return true for success or false for failure.
+   */
+  bool getCreateDateTime(uint16_t* pdate, uint16_t* ptime);
+  /** \return All error bits. */
+  uint8_t getError() const {return m_error;}
+  /** Get a file's modify date and time.
+   *
+   * \param[out] pdate Packed date for directory entry.
+   * \param[out] ptime Packed time for directory entry.
+   *
+   * \return true for success or false for failure.
+   */
+  bool getModifyDateTime(uint16_t* pdate, uint16_t* ptime);
+  /**
+   * Get a file's name followed by a zero byte.
+   *
+   * \param[out] name An array of characters for the file's name.
+   * \param[in] size The size of the array in bytes. The array
+   *             must be at least 13 bytes long.  The file's name will be
+   *             truncated if the file's name is too long.
+   * \return length for success or zero for failure.
+   */
+  size_t getName(char* name, size_t size);
+  /**
+   * Get a file's ASCII name followed by a zero.
+   *
+   * \param[out] name An array of characters for the file's name.
+   * \param[in] size The size of the array in characters.
+   * \return the name length.
+   */
+  size_t getName7(char* name, size_t size);
+  /**
+   * Get a file's UTF-8 name followed by a zero.
+   *
+   * \param[out] name An array of characters for the file's name.
+   * \param[in] size The size of the array in characters.
+   * \return the name length.
+   */
+  size_t getName8(char* name, size_t size);
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+  size_t __attribute__((error("use getSFN(name, size)"))) getSFN(char* name);
+#endif  // DOXYGEN_SHOULD_SKIP_THIS
+  /**
+   * Get a file's Short File Name followed by a zero byte.
+   *
+   * \param[out] name An array of characters for the file's name.
+   *                  The array should be at least 13 bytes long.
+   * \param[in] size size of name array.
+   * \return true for success or false for failure.
+   */
+  size_t getSFN(char* name, size_t size);
+  /** \return value of writeError */
+  bool getWriteError() const {
+    return isOpen() ? m_error & WRITE_ERROR : true;
+  }
+  /**
+   * Check for device busy.
+   *
+   * \return true if busy else false.
+   */
+  bool isBusy();
+#if USE_FAT_FILE_FLAG_CONTIGUOUS
+    /** \return True if the file is contiguous. */
+  bool isContiguous() const {return m_flags & FILE_FLAG_CONTIGUOUS;}
+#endif  // USE_FAT_FILE_FLAG_CONTIGUOUS
+  /** \return True if this is a directory. */
+  bool isDir() const {return m_attributes & FILE_ATTR_DIR;}
+  /** \return True if this is a normal file. */
+  bool isFile() const {return m_attributes & FILE_ATTR_FILE;}
+  /** \return True if this is a normal file or sub-directory. */
+  bool isFileOrSubDir() const {return isFile() || isSubDir();}
+  /** \return True if this is a hidden file. */
+  bool isHidden() const {return m_attributes & FS_ATTRIB_HIDDEN;}
+  /** \return true if this file has a Long File Name. */
+  bool isLFN() const {return m_lfnOrd;}
+  /** \return True if this is an open file/directory. */
+  bool isOpen() const {return m_attributes;}
+  /** \return True file is readable. */
+  bool isReadable() const {return m_flags & FILE_FLAG_READ;}
+  /** \return True if file is read-only */
+  bool isReadOnly() const {return m_attributes & FS_ATTRIB_READ_ONLY;}
+  /** \return True if this is the root directory. */
+  bool isRoot() const {return m_attributes & FILE_ATTR_ROOT;}
+  /** \return True if this is the FAT32 root directory. */
+  bool isRoot32() const {return m_attributes & FILE_ATTR_ROOT32;}
+  /** \return True if this is the FAT12 of FAT16 root directory. */
+  bool isRootFixed() const {return m_attributes & FILE_ATTR_ROOT_FIXED;}
+  /** \return True if this is a sub-directory. */
+  bool isSubDir() const {return m_attributes & FILE_ATTR_SUBDIR;}
+  /** \return True if this is a system file. */
+  bool isSystem() const {return m_attributes & FS_ATTRIB_SYSTEM;}
+  /** \return True file is writable. */
+  bool isWritable() const {return m_flags & FILE_FLAG_WRITE;}
+  /** List directory contents.
+   *
+   * \param[in] pr Print stream for list.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \param[in] indent Amount of space before file name. Used for recursive
+   * list to indicate subdirectory level.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(print_t* pr, uint8_t flags = 0, uint8_t indent = 0);
+  /** Make a new directory.
+   *
+   * \param[in] dir An open FatFile instance for the directory that will
+   *                   contain the new directory.
+   *
+   * \param[in] path A path with a valid name for the new directory.
+   *
+   * \param[in] pFlag Create missing parent directories if true.
+   *
+   * \return true for success or false for failure.
+   */
+  bool mkdir(FatFile* dir, const char* path, bool pFlag = true);
+  /** Open a file in the volume root directory.
+   *
+   * \param[in] vol Volume where the file is located.
+   *
+   * \param[in] path with a valid name for a file to be opened.
+   *
+   * \param[in] oflag bitwise-inclusive OR of open flags.
+   *                  See see FatFile::open(FatFile*, const char*, uint8_t).
+   *
+   * \return true for success or false for failure.
+   */
+  bool open(FatVolume* vol, const char* path, oflag_t oflag = O_RDONLY);
+  /** Open a file by index.
+   *
+   * \param[in] dirFile An open FatFile instance for the directory.
+   *
+   * \param[in] index The \a index of the directory entry for the file to be
+   * opened.  The value for \a index is (directory file position)/32.
+   *
+   * \param[in] oflag bitwise-inclusive OR of open flags.
+   *                  See see FatFile::open(FatFile*, const char*, uint8_t).
+   *
+   * See open() by path for definition of flags.
+   * \return true for success or false for failure.
+   */
+  bool open(FatFile* dirFile, uint16_t index, oflag_t oflag = O_RDONLY);
+  /** Open a file by index in the current working directory.
+   *
+   * \param[in] index The \a index of the directory entry for the file to be
+   * opened.  The value for \a index is (directory file position)/32.
+   *
+   * \param[in] oflag bitwise-inclusive OR of open flags.
+   *                  See see FatFile::open(FatFile*, const char*, uint8_t).
+   *
+   * See open() by path for definition of flags.
+   * \return true for success or false for failure.
+   */
+  bool open(uint16_t index, oflag_t oflag = O_RDONLY);
+  /** Open a file or directory by name.
+   *
+   * \param[in] dirFile An open FatFile instance for the directory containing
+   *                    the file to be opened.
+   *
+   * \param[in] path A path with a valid name for a file to be opened.
+   *
+   * \param[in] oflag Values for \a oflag are constructed by a
+   *                  bitwise-inclusive OR of flags from the following list.
+   *                  Only one of O_RDONLY, O_READ, O_WRONLY, O_WRITE, or
+   *                  O_RDWR is allowed.
+   *
+   * O_RDONLY - Open for reading.
+   *
+   * O_READ - Same as O_RDONLY.
+   *
+   * O_WRONLY - Open for writing.
+   *
+   * O_WRITE - Same as O_WRONLY.
+   *
+   * O_RDWR - Open for reading and writing.
+   *
+   * O_APPEND - If set, the file offset shall be set to the end of the
+   * file prior to each write.
+   *
+   * O_AT_END - Set the initial position at the end of the file.
+   *
+   * O_CREAT - If the file exists, this flag has no effect except as noted
+   * under O_EXCL below. Otherwise, the file shall be created
+   *
+   * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file
+   * exists.
+   *
+   * O_TRUNC - If the file exists and is a regular file, and the file is
+   * successfully opened and is not read only, its length shall be truncated
+   * to 0.
+   *
+   * WARNING: A given file must not be opened by more than one FatFile object
+   * or file corruption may occur.
+   *
+   * \note Directory files must be opened read only.  Write and truncation is
+   * not allowed for directory files.
+   *
+   * \return true for success or false for failure.
+   */
+  bool open(FatFile* dirFile, const char* path, oflag_t oflag = O_RDONLY);
+  /** Open a file in the current working volume.
+   *
+   * \param[in] path A path with a valid name for a file to be opened.
+   *
+   * \param[in] oflag bitwise-inclusive OR of open flags.
+   *                  See see FatFile::open(FatFile*, const char*, uint8_t).
+   *
+   * \return true for success or false for failure.
+   */
+  bool open(const char* path, oflag_t oflag = O_RDONLY);
+   /** Open the current working directory.
+   *
+   * \return true for success or false for failure.
+   */
+  bool openCwd();
+  /** Open existing file wih Short 8.3 names.
+   * \param[in] path with short 8.3 names.
+   *
+   * the purpose of this function is to save flash on Uno
+   * and other small boards.
+   *
+   * Directories will be opened O_RDONLY, files O_RDWR.
+   * \return true for success or false for failure.
+   */
+  bool openExistingSFN(const char* path);
+  /** Open the next file or subdirectory in a directory.
+   *
+   * \param[in] dirFile An open FatFile instance for the directory
+   *                    containing the file to be opened.
+   *
+   * \param[in] oflag bitwise-inclusive OR of open flags.
+   *                  See see FatFile::open(FatFile*, const char*, uint8_t).
+   *
+   * \return true for success or false for failure.
+   */
+  bool openNext(FatFile* dirFile, oflag_t oflag = O_RDONLY);
+  /** Open a volume's root directory.
+   *
+   * \param[in] vol The FAT volume containing the root directory to be opened.
+   *
+   * \return true for success or false for failure.
+   */
+  bool openRoot(FatVolume* vol);
+
+  /** Return the next available byte without consuming it.
+   *
+   * \return The byte if no error and not at eof else -1;
+   */
+  int peek();
+  /** Allocate contiguous clusters to an empty file.
+   *
+   * The file must be empty with no clusters allocated.
+   *
+   * The file will contain uninitialized data.
+   *
+   * \param[in] length size of the file in bytes.
+   * \return true for success or false for failure.
+   */
+  bool preAllocate(uint32_t length);
+  /** Print a file's access date
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return The number of characters printed.
+   */
+  size_t printAccessDate(print_t* pr);
+  /** Print a file's access date
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return The number of characters printed.
+   */
+  size_t printAccessDateTime(print_t* pr) {
+    return printAccessDate(pr);
+  }
+  /** Print a file's creation date and time
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return The number of bytes printed.
+   */
+  size_t printCreateDateTime(print_t* pr);
+  /** %Print a directory date field.
+   *
+   *  Format is yyyy-mm-dd.
+   *
+   * \param[in] pr Print stream for output.
+   * \param[in] fatDate The date field from a directory entry.
+   */
+  static void printFatDate(print_t* pr, uint16_t fatDate);
+  /** %Print a directory time field.
+   *
+   * Format is hh:mm:ss.
+   *
+   * \param[in] pr Print stream for output.
+   * \param[in] fatTime The time field from a directory entry.
+   */
+  static void printFatTime(print_t* pr, uint16_t fatTime);
+  /** Print a number followed by a field terminator.
+   * \param[in] value The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \param[in] prec Number of digits after decimal point.
+   * \return The number of bytes written or -1 if an error occurs.
+   */
+  size_t printField(double value, char term, uint8_t prec = 2) {
+    char buf[24];
+    char* str = buf + sizeof(buf);
+    if (term) {
+      *--str = term;
+      if (term == '\n') {
+        *--str = '\r';
+      }
+    }
+    str = fmtDouble(str, value, prec, false);
+    return write(str, buf + sizeof(buf) - str);
+  }
+  /** Print a number followed by a field terminator.
+   * \param[in] value The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \param[in] prec Number of digits after decimal point.
+   * \return The number of bytes written or -1 if an error occurs.
+   */
+  size_t printField(float value, char term, uint8_t prec = 2) {
+    return printField(static_cast<double>(value), term, prec);
+  }
+  /** Print a number followed by a field terminator.
+   * \param[in] value The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \return The number of bytes written or -1 if an error occurs.
+   */
+  template <typename Type>
+  size_t printField(Type value, char term) {
+    char sign = 0;
+    char buf[3*sizeof(Type) + 3];
+    char* str = buf + sizeof(buf);
+
+    if (term) {
+      *--str = term;
+      if (term == '\n') {
+        *--str = '\r';
+      }
+    }
+    if (value < 0) {
+      value = -value;
+      sign = '-';
+    }
+    if (sizeof(Type) < 4) {
+      str = fmtBase10(str, (uint16_t)value);
+    } else {
+      str = fmtBase10(str, (uint32_t)value);
+    }
+    if (sign) {
+      *--str = sign;
+    }
+    return write(str, &buf[sizeof(buf)] - str);
+  }
+  /** Print a file's size.
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return The number of characters printed is returned
+   *         for success and zero is returned for failure.
+   */
+  size_t printFileSize(print_t* pr);
+  /** Print a file's modify date and time
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return The number of characters printed.
+   */
+  size_t printModifyDateTime(print_t* pr);
+  /** Print a file's name
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return length for success or zero for failure.
+   */
+  size_t printName(print_t* pr);
+  /** Print a file's ASCII name
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return true for success or false for failure.
+   */
+  size_t printName7(print_t* pr);
+  /** Print a file's UTF-8 name
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return true for success or false for failure.
+   */
+  size_t printName8(print_t* pr);
+  /** Print a file's Short File Name.
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return The number of characters printed is returned
+   *         for success and zero is returned for failure.
+   */
+  size_t printSFN(print_t* pr);
+  /** Read the next byte from a file.
+   *
+   * \return For success read returns the next byte in the file as an int.
+   * If an error occurs or end of file is reached -1 is returned.
+   */
+  int read() {
+    uint8_t b;
+    return read(&b, 1) == 1 ? b : -1;
+  }
+  /** Read data from a file starting at the current position.
+   *
+   * \param[out] buf Pointer to the location that will receive the data.
+   *
+   * \param[in] count Maximum number of bytes to read.
+   *
+   * \return For success read() returns the number of bytes read.
+   * A value less than \a nbyte, including zero, will be returned
+   * if end of file is reached.
+   * If an error occurs, read() returns -1.
+   */
+  int read(void* buf, size_t count);
+  /** Read the next directory entry from a directory file.
+   *
+   * \param[out] dir The DirFat_t struct that will receive the data.
+   *
+   * \return For success readDir() returns the number of bytes read.
+   * A value of zero will be returned if end of file is reached.
+   * If an error occurs, readDir() returns -1.  Possible errors include
+   * readDir() called before a directory has been opened, this is not
+   * a directory file or an I/O error occurred.
+   */
+  int8_t readDir(DirFat_t* dir);
+  /** Remove a file.
+   *
+   * The directory entry and all data for the file are deleted.
+   *
+   * \note This function should not be used to delete the 8.3 version of a
+   * file that has a long name. For example if a file has the long name
+   * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
+   *
+   * \return true for success or false for failure.
+   */
+  bool remove();
+  /** Remove a file.
+   *
+   * The directory entry and all data for the file are deleted.
+   *
+   * \param[in] path Path for the file to be removed.
+   *
+   * Example use: dirFile.remove(filenameToRemove);
+   *
+   * \note This function should not be used to delete the 8.3 version of a
+   * file that has a long name. For example if a file has the long name
+   * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
+   *
+   * \return true for success or false for failure.
+   */
+  bool remove(const char* path);
+  /** Rename a file or subdirectory.
+   * \note the renamed file will be moved to the current volume working
+   * directory.
+   *
+   * \param[in] newPath New path name for the file/directory.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rename(const char* newPath);
+  /** Rename a file or subdirectory.
+   *
+   * \param[in] dirFile Directory for the new path.
+   * \param[in] newPath New path name for the file/directory.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rename(FatFile* dirFile, const char* newPath);
+  /** Set the file's current position to zero. */
+  void rewind() {
+    seekSet(0);
+  }
+  /** Remove a directory file.
+   *
+   * The directory file will be removed only if it is empty and is not the
+   * root directory.  rmdir() follows DOS and Windows and ignores the
+   * read-only attribute for the directory.
+   *
+   * \note This function should not be used to delete the 8.3 version of a
+   * directory that has a long name. For example if a directory has the
+   * long name "New folder" you should not delete the 8.3 name "NEWFOL~1".
+   *
+   * \return true for success or false for failure.
+   */
+  bool rmdir();
+  /** Recursively delete a directory and all contained files.
+   *
+   * This is like the Unix/Linux 'rm -rf *' if called with the root directory
+   * hence the name.
+   *
+   * Warning - This will remove all contents of the directory including
+   * subdirectories.  The directory will then be removed if it is not root.
+   * The read-only attribute for files will be ignored.
+   *
+   * \note This function should not be used to delete the 8.3 version of
+   * a directory that has a long name.  See remove() and rmdir().
+   *
+   * \return true for success or false for failure.
+   */
+  bool rmRfStar();
+  /** Set the files position to current position + \a pos. See seekSet().
+   * \param[in] offset The new position in bytes from the current position.
+   * \return true for success or false for failure.
+   */
+  bool seekCur(int32_t offset) {
+    return seekSet(m_curPosition + offset);
+  }
+  /** Set the files position to end-of-file + \a offset. See seekSet().
+   * Can't be used for directory files since file size is not defined.
+   * \param[in] offset The new position in bytes from end-of-file.
+   * \return true for success or false for failure.
+   */
+  bool seekEnd(int32_t offset = 0) {
+    return isFile() ? seekSet(m_fileSize + offset) : false;
+  }
+  /** Sets a file's position.
+   *
+   * \param[in] pos The new position in bytes from the beginning of the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool seekSet(uint32_t pos);
+  /** The sync() call causes all modified data and directory fields
+   * to be written to the storage device.
+   *
+   * \return true for success or false for failure.
+   */
+  bool sync();
+  /** Set a file's timestamps in its directory entry.
+   *
+   * \param[in] flags Values for \a flags are constructed by a bitwise-inclusive
+   * OR of flags from the following list
+   *
+   * T_ACCESS - Set the file's last access date.
+   *
+   * T_CREATE - Set the file's creation date and time.
+   *
+   * T_WRITE - Set the file's last write/modification date and time.
+   *
+   * \param[in] year Valid range 1980 - 2107 inclusive.
+   *
+   * \param[in] month Valid range 1 - 12 inclusive.
+   *
+   * \param[in] day Valid range 1 - 31 inclusive.
+   *
+   * \param[in] hour Valid range 0 - 23 inclusive.
+   *
+   * \param[in] minute Valid range 0 - 59 inclusive.
+   *
+   * \param[in] second Valid range 0 - 59 inclusive
+   *
+   * \note It is possible to set an invalid date since there is no check for
+   * the number of days in a month.
+   *
+   * \note
+   * Modify and access timestamps may be overwritten if a date time callback
+   * function has been set by dateTimeCallback().
+   *
+   * \return true for success or false for failure.
+   */
+  bool timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day,
+                 uint8_t hour, uint8_t minute, uint8_t second);
+
+  /** Truncate a file at the current file position.
+   * will be maintained if it is less than or equal to \a length otherwise
+   * it will be set to end of file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool truncate();
+  /** Truncate a file to a specified length.  The current file position
+   * will be set to end of file.
+   *
+   * \param[in] length The desired length for the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool truncate(uint32_t length) {
+    return seekSet(length) && truncate();
+  }
+  /** Write a string to a file. Used by the Arduino Print class.
+   * \param[in] str Pointer to the string.
+   * Use getWriteError to check for errors.
+   * \return count of characters written for success or -1 for failure.
+   */
+  size_t write(const char* str) {
+    return write(str, strlen(str));
+  }
+  /** Write a single byte.
+   * \param[in] b The byte to be written.
+   * \return +1 for success or -1 for failure.
+   */
+  size_t write(uint8_t b) {
+    return write(&b, 1);
+  }
+  /** Write data to an open file.
+   *
+   * \note Data is moved to the cache but may not be written to the
+   * storage device until sync() is called.
+   *
+   * \param[in] buf Pointer to the location of the data to be written.
+   *
+   * \param[in] count Number of bytes to write.
+   *
+   * \return For success write() returns the number of bytes written, always
+   * \a count.  If an error occurs, write() returns zero and writeError is set.
+   *
+   */
+  size_t write(const void* buf, size_t count);
+//------------------------------------------------------------------------------
+#if ENABLE_ARDUINO_SERIAL
+  /** List directory contents.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(uint8_t flags = 0) {
+    return ls(&Serial, flags);
+  }
+  /** Print a file's name.
+   *
+   * \return length for success or zero for failure.
+   */
+  size_t printName() {
+    return FatFile::printName(&Serial);
+  }
+#endif  // ENABLE_ARDUINO_SERIAL
+
+ private:
+  /** FatVolume allowed access to private members. */
+  friend class FatVolume;
+
+  /** This file has not been opened. */
+  static const uint8_t FILE_ATTR_CLOSED = 0;
+  /** Entry for normal data file */
+  static const uint8_t FILE_ATTR_FILE = 0X08;
+  /** Entry is for a subdirectory */
+  static const uint8_t FILE_ATTR_SUBDIR = FS_ATTRIB_DIRECTORY;
+  /** A FAT12 or FAT16 root directory */
+  static const uint8_t FILE_ATTR_ROOT_FIXED = 0X40;
+  /** A FAT32 root directory */
+  static const uint8_t FILE_ATTR_ROOT32 = 0X80;
+  /** Entry is for root. */
+  static const uint8_t FILE_ATTR_ROOT =
+                       FILE_ATTR_ROOT_FIXED | FILE_ATTR_ROOT32;
+  /** Directory type bits */
+  static const uint8_t FILE_ATTR_DIR = FILE_ATTR_SUBDIR | FILE_ATTR_ROOT;
+
+  // private functions
+
+  bool addCluster();
+  bool addDirCluster();
+  DirFat_t* cacheDir(uint16_t index) {
+    return seekSet(32UL*index) ? readDirCache() : nullptr;
+  }
+  DirFat_t* cacheDirEntry(uint8_t action);
+  bool cmpName(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd);
+  bool createLFN(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd);
+  uint16_t getLfnChar(DirLfn_t* ldir, uint8_t i);
+  uint8_t lfnChecksum(uint8_t* name) {
+    uint8_t sum = 0;
+    for (uint8_t i = 0; i < 11; i++) {
+        sum = (((sum & 1) << 7) | (sum >> 1)) + name[i];
+    }
+    return sum;
+  }
+  static bool makeSFN(FatLfn_t* fname);
+  bool makeUniqueSfn(FatLfn_t* fname);
+  bool openCluster(FatFile* file);
+  bool parsePathName(const char* str, FatLfn_t* fname, const char** ptr);
+  bool parsePathName(const char* str, FatSfn_t* fname, const char** ptr);
+  bool mkdir(FatFile* parent, FatName_t* fname);
+  bool open(FatFile* dirFile, FatLfn_t* fname, oflag_t oflag);
+  bool open(FatFile* dirFile, FatSfn_t* fname, oflag_t oflag);
+  bool openSFN(FatSfn_t* fname);
+  bool openCachedEntry(FatFile* dirFile, uint16_t cacheIndex, oflag_t oflag,
+                       uint8_t lfnOrd);
+  DirFat_t* readDirCache(bool skipReadOk = false);
+
+  // bits defined in m_flags
+  static const uint8_t FILE_FLAG_READ = 0X01;
+  static const uint8_t FILE_FLAG_WRITE = 0X02;
+  static const uint8_t FILE_FLAG_APPEND = 0X08;
+  // treat curPosition as valid length.
+  static const uint8_t FILE_FLAG_PREALLOCATE = 0X20;
+  // file is contiguous
+  static const uint8_t FILE_FLAG_CONTIGUOUS  = 0X40;
+  // sync of directory entry required
+  static const uint8_t FILE_FLAG_DIR_DIRTY = 0X80;
+
+  // private data
+  static const uint8_t WRITE_ERROR = 0X1;
+  static const uint8_t READ_ERROR  = 0X2;
+
+  uint8_t    m_attributes = FILE_ATTR_CLOSED;
+  uint8_t    m_error = 0;        // Error bits.
+  uint8_t    m_flags = 0;        // See above for definition of m_flags bits
+  uint8_t    m_lfnOrd;
+  uint16_t   m_dirIndex;         // index of directory entry in dir file
+  FatVolume* m_vol;              // volume where file is located
+  uint32_t   m_dirCluster;
+  uint32_t   m_curCluster;       // cluster for current file position
+  uint32_t   m_curPosition;      // current file position
+  uint32_t   m_dirSector;        // sector for this files directory entry
+  uint32_t   m_fileSize;         // file size in bytes
+  uint32_t   m_firstCluster;     // first cluster of file
+};
+
+#include "../common/ArduinoFiles.h"
+/**
+ * \class File32
+ * \brief FAT16/FAT32 file with Arduino Stream.
+ */
+class File32 : public StreamFile<FatFile, uint32_t> {
+ public:
+   /** Opens the next file or folder in a directory.
+   *
+   * \param[in] oflag open flags.
+   * \return a FatStream object.
+   */
+  File32 openNextFile(oflag_t oflag = O_RDONLY) {
+    File32 tmpFile;
+    tmpFile.openNext(this, oflag);
+    return tmpFile;
+  }
+};
+#endif  // FatFile_h

+ 595 - 0
lib/SdFat_NoArduino/src/FatLib/FatFileLFN.cpp

@@ -0,0 +1,595 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "FatFileLFN.cpp"
+#include "../common/DebugMacros.h"
+#include "../common/upcase.h"
+#include "../common/FsUtf.h"
+#include "FatLib.h"
+#if USE_LONG_FILE_NAMES
+//------------------------------------------------------------------------------
+static bool isLower(char c) {
+  return 'a' <= c && c <= 'z';
+}
+//------------------------------------------------------------------------------
+static bool isUpper(char c) {
+  return 'A' <= c && c <= 'Z';
+}
+//------------------------------------------------------------------------------
+// A bit smaller than toupper in AVR 328.
+inline char toUpper(char c) {
+  return isLower(c) ? c - 'a' + 'A' : c;
+}
+//------------------------------------------------------------------------------
+/**
+ * Store a 16-bit long file name character.
+ *
+ * \param[in] ldir Pointer to long file name directory entry.
+ * \param[in] i Index of character.
+ * \param[in] c The 16-bit character.
+ */
+static void putLfnChar(DirLfn_t* ldir, uint8_t i, uint16_t c) {
+  if (i < 5) {
+    setLe16(ldir->unicode1 + 2*i, c);
+  } else if (i < 11) {
+    setLe16(ldir->unicode2 + 2*i -10, c);
+  } else if (i < 13) {
+    setLe16(ldir->unicode3 + 2*i - 22, c);
+  }
+}
+//------------------------------------------------------------------------------
+// Daniel Bernstein University of Illinois at Chicago.
+// Original had + instead of ^
+__attribute__((unused))
+static uint16_t Bernstein(const char* bgn, const char* end, uint16_t hash) {
+  while (bgn < end) {
+    // hash = hash * 33 ^ str[i];
+    hash = ((hash << 5) + hash) ^ (*bgn++);
+  }
+  return hash;
+}
+//==============================================================================
+bool FatFile::cmpName(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd) {
+  FatFile dir = *this;
+  DirLfn_t* ldir;
+  fname->reset();
+  for (uint8_t order = 1; order <= lfnOrd; order++) {
+    ldir = reinterpret_cast<DirLfn_t*>(dir.cacheDir(index - order));
+    if (!ldir) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    // These should be checked in caller.
+    DBG_HALT_IF(ldir->attributes != FAT_ATTRIB_LONG_NAME);
+    DBG_HALT_IF(order != (ldir->order & 0X1F));
+    for (uint8_t i = 0; i < 13; i++) {
+      uint16_t u = getLfnChar(ldir, i);
+      if (fname->atEnd()) {
+        return u == 0;
+      }
+#if USE_UTF8_LONG_NAMES
+      uint16_t cp = fname->get16();
+      // Make sure caller checked for valid UTF-8.
+      DBG_HALT_IF(cp == 0XFFFF);
+      if (toUpcase(u) != toUpcase(cp)) {
+        return false;
+      }
+#else  // USE_UTF8_LONG_NAMES
+      if (u > 0X7F || toUpper(u) != toUpper(fname->getch())) {
+        return false;
+      }
+#endif  // USE_UTF8_LONG_NAMES
+    }
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::createLFN(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd) {
+  FatFile dir = *this;
+  DirLfn_t* ldir;
+  uint8_t checksum = lfnChecksum(fname->sfn);
+  uint8_t fc = 0;
+  fname->reset();
+
+  for (uint8_t order = 1; order <= lfnOrd; order++) {
+    ldir = reinterpret_cast<DirLfn_t*>(dir.cacheDir(index - order));
+    if (!ldir) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    dir.m_vol->cacheDirty();
+    ldir->order = order == lfnOrd ? FAT_ORDER_LAST_LONG_ENTRY | order : order;
+    ldir->attributes = FAT_ATTRIB_LONG_NAME;
+    ldir->mustBeZero1 = 0;
+    ldir->checksum = checksum;
+    setLe16(ldir->mustBeZero2, 0);
+    for (uint8_t i = 0; i < 13; i++) {
+      uint16_t cp;
+      if (fname->atEnd()) {
+        cp = fc++ ? 0XFFFF : 0;
+      } else {
+        cp = fname->get16();
+        // Verify caller checked for valid UTF-8.
+        DBG_HALT_IF(cp == 0XFFFF);
+      }
+      putLfnChar(ldir, i, cp);
+    }
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::makeSFN(FatLfn_t* fname) {
+  bool is83;
+//  char c;
+  uint8_t c;
+  uint8_t bit = FAT_CASE_LC_BASE;
+  uint8_t lc = 0;
+  uint8_t uc = 0;
+  uint8_t i = 0;
+  uint8_t in = 7;
+  const char* dot;
+  const char* end = fname->end;
+  const char* ptr = fname->begin;
+
+  // Assume not zero length.
+  DBG_HALT_IF(end == ptr);
+  // Assume blanks removed from start and end.
+  DBG_HALT_IF(*ptr == ' ' || *(end - 1) == ' ' || *(end - 1) == '.');
+
+  // Blank file short name.
+  for (uint8_t k = 0; k < 11; k++) {
+    fname->sfn[k] = ' ';
+  }
+  // Not 8.3 if starts with dot.
+  is83 = *ptr == '.' ? false : true;
+  // Skip leading dots.
+  for (; *ptr == '.'; ptr++) {}
+  // Find last dot.
+  for (dot = end - 1; dot > ptr && *dot != '.'; dot--) {}
+
+  for (; ptr < end; ptr++) {
+    c = *ptr;
+    if (c == '.' && ptr == dot) {
+      in = 10;  // Max index for full 8.3 name.
+      i = 8;    // Place for extension.
+      bit = FAT_CASE_LC_EXT;  // bit for extension.
+    } else {
+      if (sfnReservedChar(c)) {
+        is83 = false;
+        // Skip UTF-8 trailing characters.
+        if ((c & 0XC0) == 0X80) {
+          continue;
+        }
+        c = '_';
+      }
+      if (i > in) {
+        is83 = false;
+        if (in == 10 || ptr > dot) {
+         // Done - extension longer than three characters or no extension.
+          break;
+        }
+        // Skip to dot.
+        ptr = dot - 1;
+        continue;
+      }
+      if (isLower(c)) {
+        c += 'A' - 'a';
+        lc |= bit;
+      } else if (isUpper(c)) {
+        uc |= bit;
+      }
+      fname->sfn[i++] = c;
+      if (i < 7) {
+        fname->seqPos = i;
+      }
+    }
+  }
+  if (fname->sfn[0] == ' ') {
+    DBG_HALT_MACRO;
+    goto fail;
+  }
+  if (is83) {
+    fname->flags = lc & uc ? FNAME_FLAG_MIXED_CASE : lc;
+  } else {
+    fname->flags = FNAME_FLAG_LOST_CHARS;
+    fname->sfn[fname->seqPos] = '~';
+    fname->sfn[fname->seqPos + 1] = '1';
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::makeUniqueSfn(FatLfn_t* fname) {
+  const uint8_t FIRST_HASH_SEQ = 2;  // min value is 2
+  uint8_t pos = fname->seqPos;
+  DirFat_t* dir;
+  uint16_t hex = 0;
+
+  DBG_HALT_IF(!(fname->flags & FNAME_FLAG_LOST_CHARS));
+  DBG_HALT_IF(fname->sfn[pos] != '~' && fname->sfn[pos + 1] != '1');
+
+  for (uint8_t seq = FIRST_HASH_SEQ; seq < 100; seq++) {
+     DBG_WARN_IF(seq > FIRST_HASH_SEQ);
+#ifdef USE_LFN_HASH
+    hex = Bernstein(fname->begin, fname->end, seq);
+#else
+    hex += millis();
+#endif
+    if (pos > 3) {
+      // Make space in name for ~HHHH.
+      pos = 3;
+    }
+    for (uint8_t i = pos + 4 ; i > pos; i--) {
+      uint8_t h = hex & 0XF;
+      fname->sfn[i] = h < 10 ? h + '0' : h + 'A' - 10;
+      hex >>= 4;
+    }
+    fname->sfn[pos] = '~';
+    rewind();
+    while (1) {
+      dir = readDirCache(true);
+      if (!dir) {
+        if (!getError()) {
+          // At EOF and name not found if no error.
+          goto done;
+        }
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      if (dir->name[0] == FAT_NAME_FREE) {
+        goto done;
+      }
+      if (isFatFileOrSubdir(dir) && !memcmp(fname->sfn, dir->name, 11)) {
+        // Name found - try another.
+        break;
+      }
+    }
+  }
+  // fall inti fail - too many tries.
+  DBG_FAIL_MACRO;
+
+ fail:
+  return false;
+
+ done:
+  return true;
+}
+//------------------------------------------------------------------------------
+bool FatFile::open(FatFile* dirFile, FatLfn_t* fname, oflag_t oflag) {
+  bool fnameFound = false;
+  uint8_t lfnOrd = 0;
+  uint8_t freeNeed;
+  uint8_t freeFound = 0;
+  uint8_t order = 0;
+  uint8_t checksum = 0;
+  uint8_t ms10;
+  uint8_t nameOrd;
+  uint16_t freeIndex = 0;
+  uint16_t curIndex;
+  uint16_t date;
+  uint16_t time;
+  DirFat_t* dir;
+  DirLfn_t* ldir;
+  auto vol = dirFile->m_vol;
+
+  if (!dirFile->isDir() || isOpen()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Number of directory entries needed.
+  nameOrd = (fname->len + 12)/13;
+  freeNeed = fname->flags & FNAME_FLAG_NEED_LFN ? 1 + nameOrd : 1;
+  dirFile->rewind();
+  while (1) {
+    curIndex = dirFile->m_curPosition/FS_DIR_SIZE;
+    dir = dirFile->readDirCache();
+    if (!dir) {
+      if (dirFile->getError()) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      // At EOF
+      goto create;
+    }
+    if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == FAT_NAME_FREE) {
+      if (freeFound == 0) {
+        freeIndex = curIndex;
+      }
+      if (freeFound < freeNeed) {
+        freeFound++;
+      }
+      if (dir->name[0] == FAT_NAME_FREE) {
+        goto create;
+      }
+    } else {
+      if (freeFound < freeNeed) {
+        freeFound = 0;
+      }
+    }
+    // skip empty slot or '.' or '..'
+    if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') {
+      lfnOrd = 0;
+    } else if (isFatLongName(dir)) {
+      ldir = reinterpret_cast<DirLfn_t*>(dir);
+      if (!lfnOrd) {
+        order = ldir->order & 0X1F;
+        if (order != nameOrd ||
+          (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) == 0) {
+          continue;
+        }
+        lfnOrd = nameOrd;
+        checksum = ldir->checksum;
+      } else if (ldir->order != --order || checksum != ldir->checksum) {
+        lfnOrd = 0;
+        continue;
+      }
+      if (order == 1) {
+        if (!dirFile->cmpName(curIndex + 1, fname, lfnOrd)) {
+          lfnOrd = 0;
+        }
+      }
+    } else if (isFatFileOrSubdir(dir)) {
+      if (lfnOrd) {
+        if (1 == order && lfnChecksum(dir->name) == checksum) {
+          goto found;
+        }
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      if (!memcmp(dir->name, fname->sfn, sizeof(fname->sfn))) {
+        if (!(fname->flags & FNAME_FLAG_LOST_CHARS)) {
+          goto found;
+        }
+        fnameFound = true;
+      }
+    } else {
+      lfnOrd = 0;
+    }
+  }
+
+ found:
+  // Don't open if create only.
+  if (oflag & O_EXCL) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  goto open;
+
+ create:
+  // don't create unless O_CREAT and write mode
+  if (!(oflag & O_CREAT) || !isWriteMode(oflag)) {
+    DBG_WARN_MACRO;
+    goto fail;
+  }
+  // Keep found entries or start at current index if no free entries found.
+  if (freeFound == 0) {
+    freeIndex = curIndex;
+  }
+
+  while (freeFound < freeNeed) {
+    dir = dirFile->readDirCache();
+    if (!dir) {
+      if (dirFile->getError()) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      // EOF if no error.
+      break;
+    }
+    freeFound++;
+  }
+  while (freeFound < freeNeed) {
+    // Will fail if FAT16 root.
+    if (!dirFile->addDirCluster()) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    freeFound += vol->dirEntriesPerCluster();
+  }
+  if (fnameFound) {
+    if (!dirFile->makeUniqueSfn(fname)) {
+      goto fail;
+    }
+  }
+  lfnOrd = freeNeed - 1;
+  curIndex = freeIndex + lfnOrd;
+  if (!dirFile->createLFN(curIndex, fname, lfnOrd)) {
+    goto fail;
+  }
+  dir = dirFile->cacheDir(curIndex);
+  if (!dir) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // initialize as empty file
+  memset(dir, 0, sizeof(DirFat_t));
+  memcpy(dir->name, fname->sfn, 11);
+
+  // Set base-name and extension lower case bits.
+  dir->caseFlags = (FAT_CASE_LC_BASE | FAT_CASE_LC_EXT) & fname->flags;
+
+  // Set timestamps.
+  if (FsDateTime::callback) {
+    // call user date/time function
+    FsDateTime::callback(&date, &time, &ms10);
+    setLe16(dir->createDate, date);
+    setLe16(dir->createTime, time);
+    dir->createTimeMs = ms10;
+  } else {
+    setLe16(dir->createDate, FS_DEFAULT_DATE);
+    setLe16(dir->modifyDate, FS_DEFAULT_DATE);
+    setLe16(dir->accessDate, FS_DEFAULT_DATE);
+    if (FS_DEFAULT_TIME) {
+      setLe16(dir->createTime, FS_DEFAULT_TIME);
+      setLe16(dir->modifyTime, FS_DEFAULT_TIME);
+    }
+  }
+  // Force write of entry to device.
+  vol->cacheDirty();
+
+ open:
+  // open entry in cache.
+  if (!openCachedEntry(dirFile, curIndex, oflag, lfnOrd)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::parsePathName(const char* path,
+                            FatLfn_t* fname, const char** ptr) {
+  size_t len = 0;
+  // Skip leading spaces.
+  while (*path == ' ') {
+    path++;
+  }
+  fname->begin = path;
+  fname->len = 0;
+  while (*path && !isDirSeparator(*path)) {
+#if USE_UTF8_LONG_NAMES
+    uint32_t cp;
+    // Allow end = path + 4 since path is zero terminated.
+    path = FsUtf::mbToCp(path, path + 4, &cp);
+    if (!path) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    len += cp <= 0XFFFF ? 1 : 2;
+    if (cp < 0X80 && lfnReservedChar(cp)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+#else  // USE_UTF8_LONG_NAMES
+    uint8_t cp = *path++;
+    if (cp >= 0X80 || lfnReservedChar(cp)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    len++;
+#endif  // USE_UTF8_LONG_NAMES
+    if (cp != '.' && cp != ' ') {
+      // Need to trim trailing dots spaces.
+      fname->len = len;
+      fname->end = path;
+    }
+  }
+  if (!fname->len || fname->len > FAT_MAX_LFN_LENGTH) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Advance to next path component.
+  for (; *path == ' ' || isDirSeparator(*path); path++) {}
+  *ptr = path;
+  return makeSFN(fname);
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::remove() {
+  bool last;
+  uint8_t checksum;
+  FatFile dirFile;
+  DirFat_t* dir;
+  DirLfn_t* ldir;
+
+  // Cant' remove not open for write.
+  if (!isWritable()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Free any clusters.
+  if (m_firstCluster && !m_vol->freeChain(m_firstCluster)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Cache directory entry.
+  dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE);
+  if (!dir) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  checksum = lfnChecksum(dir->name);
+
+  // Mark entry deleted.
+  dir->name[0] = FAT_NAME_DELETED;
+
+  // Set this file closed.
+  m_attributes = FILE_ATTR_CLOSED;
+  m_flags = 0;
+
+  // Write entry to device.
+  if (!m_vol->cacheSync()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (!isLFN()) {
+    // Done, no LFN entries.
+    return true;
+  }
+  if (!dirFile.openCluster(this)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  for (uint8_t order = 1; order <= m_lfnOrd; order++) {
+    ldir = reinterpret_cast<DirLfn_t*>(dirFile.cacheDir(m_dirIndex - order));
+    if (!ldir) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (ldir->attributes != FAT_ATTRIB_LONG_NAME ||
+        order != (ldir->order & 0X1F) ||
+        checksum != ldir->checksum) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    last = ldir->order & FAT_ORDER_LAST_LONG_ENTRY;
+    ldir->order = FAT_NAME_DELETED;
+    m_vol->cacheDirty();
+    if (last) {
+      if (!m_vol->cacheSync()) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      return true;
+    }
+  }
+  // Fall into fail.
+  DBG_FAIL_MACRO;
+
+ fail:
+  return false;
+}
+#endif  // #if USE_LONG_FILE_NAMES

+ 109 - 0
lib/SdFat_NoArduino/src/FatLib/FatFilePrint.cpp

@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 <math.h>
+#define DBG_FILE "FatFilePrint.cpp"
+#include "../common/DebugMacros.h"
+#include "FatLib.h"
+
+//------------------------------------------------------------------------------
+bool FatFile::ls(print_t* pr, uint8_t flags, uint8_t indent) {
+  FatFile file;
+  if (!isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  rewind();
+  while (file.openNext(this, O_RDONLY)) {
+    // indent for dir level
+    if (!file.isHidden() || (flags & LS_A)) {
+      for (uint8_t i = 0; i < indent; i++) {
+        pr->write(' ');
+      }
+      if (flags & LS_DATE) {
+        file.printModifyDateTime(pr);
+        pr->write(' ');
+      }
+      if (flags & LS_SIZE) {
+        file.printFileSize(pr);
+        pr->write(' ');
+      }
+      file.printName(pr);
+      if (file.isDir()) {
+        pr->write('/');
+      }
+      pr->write('\r');
+      pr->write('\n');
+      if ((flags & LS_R) && file.isDir()) {
+        file.ls(pr, flags, indent + 2);
+      }
+    }
+    file.close();
+  }
+  if (getError()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+size_t FatFile::printAccessDate(print_t* pr) {
+  uint16_t date;
+  if (getAccessDate(&date)) {
+    return fsPrintDate(pr, date);
+  }
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t FatFile::printCreateDateTime(print_t* pr) {
+  uint16_t date;
+  uint16_t time;
+  if (getCreateDateTime(&date, &time)) {
+    return fsPrintDateTime(pr, date, time);
+  }
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t FatFile::printModifyDateTime(print_t* pr) {
+  uint16_t date;
+  uint16_t time;
+  if (getModifyDateTime(&date, &time)) {
+    return fsPrintDateTime(pr, date, time);
+  }
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t FatFile::printFileSize(print_t* pr) {
+  char buf[11];
+  char *ptr = buf + sizeof(buf);
+  *--ptr = 0;
+  ptr = fmtBase10(ptr, fileSize());
+  while (ptr > buf) {
+    *--ptr = ' ';
+  }
+  return pr->write(buf);
+}

+ 315 - 0
lib/SdFat_NoArduino/src/FatLib/FatFileSFN.cpp

@@ -0,0 +1,315 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "FatFileSFN.cpp"
+#include "../common/DebugMacros.h"
+#include "FatLib.h"
+//------------------------------------------------------------------------------
+// open with filename in fname
+#define SFN_OPEN_USES_CHKSUM 0
+bool FatFile::open(FatFile* dirFile, FatSfn_t* fname, oflag_t oflag) {
+  uint16_t date;
+  uint16_t time;
+  uint8_t ms10;
+  bool emptyFound = false;
+#if SFN_OPEN_USES_CHKSUM
+  uint8_t checksum;
+#endif  // SFN_OPEN_USES_CHKSUM
+  uint8_t lfnOrd = 0;
+  uint16_t emptyIndex = 0;
+  uint16_t index = 0;
+  DirFat_t* dir;
+  DirLfn_t* ldir;
+
+  dirFile->rewind();
+  while (true) {
+    dir = dirFile->readDirCache(true);
+    if (!dir) {
+      if (dirFile->getError())  {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      // At EOF if no error.
+      break;
+    }
+    if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == FAT_NAME_FREE) {
+      if (!emptyFound) {
+        emptyIndex = index;
+        emptyFound = true;
+      }
+      if (dir->name[0] == FAT_NAME_FREE) {
+        break;
+      }
+      lfnOrd = 0;
+    } else if (isFatFileOrSubdir(dir)) {
+      if (!memcmp(fname->sfn, dir->name, 11)) {
+        // don't open existing file if O_EXCL
+        if (oflag & O_EXCL) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+#if SFN_OPEN_USES_CHKSUM
+        if (lfnOrd && checksum != lfnChecksum(dir->name)) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+#endif  // SFN_OPEN_USES_CHKSUM
+        if (!openCachedEntry(dirFile, index, oflag, lfnOrd)) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+        return true;
+      } else {
+        lfnOrd = 0;
+      }
+    } else if (isFatLongName(dir)) {
+      ldir = reinterpret_cast<DirLfn_t*>(dir);
+      if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) {
+        lfnOrd = ldir->order & 0X1F;
+#if SFN_OPEN_USES_CHKSUM
+        checksum = ldir->checksum;
+#endif  // SFN_OPEN_USES_CHKSUM
+      }
+    } else {
+      lfnOrd = 0;
+    }
+    index++;
+  }
+  // don't create unless O_CREAT and write mode
+  if (!(oflag & O_CREAT) || !isWriteMode(oflag)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (emptyFound) {
+    index = emptyIndex;
+  } else {
+    if (!dirFile->addDirCluster()) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+  dir = reinterpret_cast<DirFat_t*>(dirFile->cacheDir(index));
+  if (!dir) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // initialize as empty file
+  memset(dir, 0, sizeof(DirFat_t));
+  memcpy(dir->name, fname->sfn, 11);
+
+  // Set base-name and extension lower case bits.
+  dir->caseFlags = (FAT_CASE_LC_BASE | FAT_CASE_LC_EXT) & fname->flags;
+
+  // Set timestamps.
+  if (FsDateTime::callback) {
+    // call user date/time function
+    FsDateTime::callback(&date, &time, &ms10);
+    setLe16(dir->createDate, date);
+    setLe16(dir->createTime, time);
+    dir->createTimeMs = ms10;
+  } else {
+    setLe16(dir->createDate, FS_DEFAULT_DATE);
+    setLe16(dir->modifyDate, FS_DEFAULT_DATE);
+    setLe16(dir->accessDate, FS_DEFAULT_DATE);
+    if (FS_DEFAULT_TIME) {
+      setLe16(dir->createTime, FS_DEFAULT_TIME);
+      setLe16(dir->modifyTime, FS_DEFAULT_TIME);
+    }
+  }
+  // Force write of entry to device.
+  dirFile->m_vol->cacheDirty();
+
+  // open entry in cache.
+  return openCachedEntry(dirFile, index, oflag, 0);
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::openExistingSFN(const char* path) {
+  FatSfn_t fname;
+  auto vol = FatVolume::cwv();
+  while (*path == '/') {
+    path++;
+  }
+  if (*path == 0) {
+    return openRoot(vol);
+  }
+  *this = *vol->vwd();
+  do {
+    if (!parsePathName(path, &fname, &path)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (!openSFN(&fname)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  } while (*path);
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FatFile::openSFN(FatSfn_t* fname) {
+  DirFat_t dir;
+  DirLfn_t* ldir;
+  auto vol = m_vol;
+  uint8_t lfnOrd = 0;
+  if (!isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  while (true) {
+    if (read(&dir, 32) != 32) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (dir.name[0] == 0) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (isFatFileOrSubdir(&dir) && memcmp(fname->sfn, dir.name, 11) == 0) {
+      uint16_t dirIndex = (m_curPosition - 32) >> 5;
+      uint32_t dirCluster = m_firstCluster;
+      memset(this, 0 , sizeof(FatFile));
+      m_attributes = dir.attributes & FS_ATTRIB_COPY;
+      m_flags = FILE_FLAG_READ;
+      if (isFatFile(&dir)) {
+        m_attributes |= FILE_ATTR_FILE;
+        if (!isReadOnly()) {
+          m_attributes |= FS_ATTRIB_ARCHIVE;
+          m_flags |= FILE_FLAG_WRITE;
+        }
+      }
+      m_lfnOrd = lfnOrd;
+      m_firstCluster = (uint32_t)getLe16(dir.firstClusterHigh) << 16;
+      m_firstCluster |= getLe16(dir.firstClusterLow);
+      m_fileSize = getLe32(dir.fileSize);
+      m_vol = vol;
+      m_dirCluster = dirCluster;
+      m_dirSector = m_vol->cacheSectorNumber();
+      m_dirIndex = dirIndex;
+      return true;
+    } else if (isFatLongName(&dir)) {
+      ldir = reinterpret_cast<DirLfn_t*>(&dir);
+      if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) {
+        lfnOrd = ldir->order & 0X1F;
+      }
+    } else {
+      lfnOrd = 0;
+    }
+  }
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+// format directory name field from a 8.3 name string
+bool FatFile::parsePathName(const char* path, FatSfn_t* fname,
+                            const char** ptr) {
+  uint8_t uc = 0;
+  uint8_t lc = 0;
+  uint8_t bit = FNAME_FLAG_LC_BASE;
+  // blank fill name and extension
+  for (uint8_t i = 0; i < 11; i++) {
+    fname->sfn[i] = ' ';
+  }
+  for (uint8_t i = 0, n = 7;; path++) {
+    uint8_t c = *path;
+    if (c == 0 || isDirSeparator(c)) {
+      // Done.
+      break;
+    }
+    if (c == '.' && n == 7) {
+      n = 10;  // max index for full 8.3 name
+      i = 8;   // place for extension
+
+      // bit for extension.
+      bit = FNAME_FLAG_LC_EXT;
+    } else {
+      if (sfnReservedChar(c) || i > n) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      if ('a' <= c && c <= 'z') {
+        c += 'A' - 'a';
+        lc |= bit;
+      } else if ('A' <= c && c <= 'Z') {
+        uc |= bit;
+      }
+      fname->sfn[i++] = c;
+    }
+  }
+  // must have a file name, extension is optional
+  if (fname->sfn[0] == ' ') {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Set base-name and extension bits.
+  fname->flags = lc & uc ? 0 : lc;
+  while (isDirSeparator(*path)) {
+    path++;
+  }
+  *ptr = path;
+  return true;
+
+ fail:
+  return false;
+}
+#if !USE_LONG_FILE_NAMES
+//------------------------------------------------------------------------------
+bool FatFile::remove() {
+  DirFat_t* dir;
+  // Can't remove if LFN or not open for write.
+  if (!isWritable() || isLFN()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Free any clusters.
+  if (m_firstCluster && !m_vol->freeChain(m_firstCluster)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Cache directory entry.
+  dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE);
+  if (!dir) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // Mark entry deleted.
+  dir->name[0] = FAT_NAME_DELETED;
+
+  // Set this file closed.
+  m_attributes = FILE_ATTR_CLOSED;
+  m_flags = 0;
+
+  // Write entry to device.
+  return m_vol->cacheSync();
+
+ fail:
+  return false;
+}
+#endif  // !USE_LONG_FILE_NAMES

+ 280 - 0
lib/SdFat_NoArduino/src/FatLib/FatFormatter.cpp

@@ -0,0 +1,280 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "FatLib.h"
+// Set nonzero to use calculated CHS in MBR.  Should not be required.
+#define USE_LBA_TO_CHS 1
+
+// Constants for file system structure optimized for flash.
+uint16_t const BU16 = 128;
+uint16_t const BU32 = 8192;
+// Assume 512 byte sectors.
+const uint16_t BYTES_PER_SECTOR = 512;
+const uint16_t SECTORS_PER_MB = 0X100000/BYTES_PER_SECTOR;
+const uint16_t FAT16_ROOT_ENTRY_COUNT = 512;
+const uint16_t FAT16_ROOT_SECTOR_COUNT =
+               32*FAT16_ROOT_ENTRY_COUNT/BYTES_PER_SECTOR;
+//------------------------------------------------------------------------------
+#define PRINT_FORMAT_PROGRESS 1
+#if !PRINT_FORMAT_PROGRESS
+#define writeMsg(str)
+#elif defined(__AVR__)
+#define writeMsg(str) if (m_pr) m_pr->print(F(str))
+#else  // PRINT_FORMAT_PROGRESS
+#define writeMsg(str) if (m_pr) m_pr->write(str)
+#endif  // PRINT_FORMAT_PROGRESS
+//------------------------------------------------------------------------------
+bool FatFormatter::format(FsBlockDevice* dev, uint8_t* secBuf, print_t* pr) {
+  bool rtn;
+  m_dev = dev;
+  m_secBuf = secBuf;
+  m_pr = pr;
+  m_sectorCount = m_dev->sectorCount();
+  m_capacityMB = (m_sectorCount + SECTORS_PER_MB - 1)/SECTORS_PER_MB;
+
+  if (m_capacityMB <= 6) {
+    writeMsg("Card is too small.\r\n");
+    return false;
+  } else if (m_capacityMB <= 16) {
+    m_sectorsPerCluster = 2;
+  } else if (m_capacityMB <= 32) {
+    m_sectorsPerCluster = 4;
+  } else if (m_capacityMB <= 64) {
+    m_sectorsPerCluster = 8;
+  } else if (m_capacityMB <= 128) {
+    m_sectorsPerCluster = 16;
+  } else if (m_capacityMB <= 1024) {
+    m_sectorsPerCluster = 32;
+  } else if (m_capacityMB <= 32768) {
+    m_sectorsPerCluster = 64;
+  } else {
+    // SDXC cards
+    m_sectorsPerCluster = 128;
+  }
+  rtn = m_sectorCount < 0X400000 ? makeFat16() : makeFat32();
+  if (rtn) {
+    writeMsg("Format Done\r\n");
+  } else {
+    writeMsg("Format Failed\r\n");
+  }
+  return rtn;
+}
+//------------------------------------------------------------------------------
+bool FatFormatter::initFatDir(uint8_t fatType, uint32_t sectorCount) {
+  size_t n;
+  memset(m_secBuf, 0, BYTES_PER_SECTOR);
+  writeMsg("Writing FAT ");
+  for (uint32_t i = 1; i < sectorCount; i++) {
+    if (!m_dev->writeSector(m_fatStart + i, m_secBuf)) {
+       return false;
+    }
+    if ((i%(sectorCount/32)) == 0) {
+      writeMsg(".");
+    }
+  }
+  writeMsg("\r\n");
+  // Allocate reserved clusters and root for FAT32.
+  m_secBuf[0] = 0XF8;
+  n = fatType == 16 ? 4 : 12;
+  for (size_t i = 1; i < n; i++) {
+    m_secBuf[i] = 0XFF;
+  }
+  return m_dev->writeSector(m_fatStart, m_secBuf) &&
+         m_dev->writeSector(m_fatStart + m_fatSize, m_secBuf);
+}
+//------------------------------------------------------------------------------
+void FatFormatter::initPbs() {
+  PbsFat_t* pbs = reinterpret_cast<PbsFat_t*>(m_secBuf);
+  memset(m_secBuf, 0, BYTES_PER_SECTOR);
+  pbs->jmpInstruction[0] = 0XEB;
+  pbs->jmpInstruction[1] = 0X76;
+  pbs->jmpInstruction[2] = 0X90;
+  for (uint8_t i = 0; i < sizeof(pbs->oemName); i++) {
+    pbs->oemName[i] = ' ';
+  }
+  setLe16(pbs->bpb.bpb16.bytesPerSector, BYTES_PER_SECTOR);
+  pbs->bpb.bpb16.sectorsPerCluster = m_sectorsPerCluster;
+  setLe16(pbs->bpb.bpb16.reservedSectorCount, m_reservedSectorCount);
+  pbs->bpb.bpb16.fatCount = 2;
+  // skip rootDirEntryCount
+  // skip totalSectors16
+  pbs->bpb.bpb16.mediaType = 0XF8;
+  // skip sectorsPerFat16
+  // skip sectorsPerTrack
+  // skip headCount
+  setLe32(pbs->bpb.bpb16.hidddenSectors, m_relativeSectors);
+  setLe32(pbs->bpb.bpb16.totalSectors32, m_totalSectors);
+  // skip rest of bpb
+  setLe16(pbs->signature, PBR_SIGNATURE);
+}
+//------------------------------------------------------------------------------
+bool FatFormatter::makeFat16() {
+  uint32_t nc;
+  uint32_t r;
+  PbsFat_t* pbs = reinterpret_cast<PbsFat_t*>(m_secBuf);
+
+  for (m_dataStart = 2*BU16; ; m_dataStart += BU16) {
+    nc = (m_sectorCount - m_dataStart)/m_sectorsPerCluster;
+    m_fatSize = (nc + 2 + (BYTES_PER_SECTOR/2) - 1)/(BYTES_PER_SECTOR/2);
+    r = BU16 + 1 + 2*m_fatSize + FAT16_ROOT_SECTOR_COUNT;
+    if (m_dataStart >= r) {
+      m_relativeSectors = m_dataStart - r + BU16;
+      break;
+    }
+  }
+  // check valid cluster count for FAT16 volume
+  if (nc < 4085 || nc >= 65525) {
+    writeMsg("Bad cluster count\r\n");
+    return false;
+  }
+  m_reservedSectorCount = 1;
+  m_fatStart = m_relativeSectors + m_reservedSectorCount;
+  m_totalSectors = nc*m_sectorsPerCluster
+                   + 2*m_fatSize + m_reservedSectorCount + 32;
+  if (m_totalSectors < 65536) {
+    m_partType = 0X04;
+  } else {
+    m_partType = 0X06;
+  }
+  // write MBR
+  if (!writeMbr()) {
+    return false;
+  }
+  initPbs();
+  setLe16(pbs->bpb.bpb16.rootDirEntryCount, FAT16_ROOT_ENTRY_COUNT);
+  setLe16(pbs->bpb.bpb16.sectorsPerFat16, m_fatSize);
+  pbs->bpb.bpb16.physicalDriveNumber = 0X80;
+  pbs->bpb.bpb16.extSignature = EXTENDED_BOOT_SIGNATURE;
+  setLe32(pbs->bpb.bpb16.volumeSerialNumber, 1234567);
+  for (size_t i = 0; i < sizeof(pbs->bpb.bpb16.volumeLabel); i++) {
+    pbs->bpb.bpb16.volumeLabel[i] = ' ';
+  }
+  pbs->bpb.bpb16.volumeType[0] = 'F';
+  pbs->bpb.bpb16.volumeType[1] = 'A';
+  pbs->bpb.bpb16.volumeType[2] = 'T';
+  pbs->bpb.bpb16.volumeType[3] = '1';
+  pbs->bpb.bpb16.volumeType[4] = '6';
+  if (!m_dev->writeSector(m_relativeSectors, m_secBuf)) {
+    return false;
+  }
+  return initFatDir(16, m_dataStart - m_fatStart);
+}
+//------------------------------------------------------------------------------
+bool FatFormatter::makeFat32() {
+  uint32_t nc;
+  uint32_t r;
+  PbsFat_t* pbs = reinterpret_cast<PbsFat_t*>(m_secBuf);
+  FsInfo_t* fsi = reinterpret_cast<FsInfo_t*>(m_secBuf);
+
+  m_relativeSectors = BU32;
+  for (m_dataStart = 2*BU32; ; m_dataStart += BU32) {
+    nc = (m_sectorCount - m_dataStart)/m_sectorsPerCluster;
+    m_fatSize = (nc + 2 + (BYTES_PER_SECTOR/4) - 1)/(BYTES_PER_SECTOR/4);
+    r = m_relativeSectors + 9 + 2*m_fatSize;
+    if (m_dataStart >= r) {
+      break;
+    }
+  }
+  // error if too few clusters in FAT32 volume
+  if (nc < 65525) {
+    writeMsg("Bad cluster count\r\n");
+    return false;
+  }
+  m_reservedSectorCount = m_dataStart - m_relativeSectors - 2*m_fatSize;
+  m_fatStart = m_relativeSectors + m_reservedSectorCount;
+  m_totalSectors = nc*m_sectorsPerCluster + m_dataStart - m_relativeSectors;
+  // type depends on address of end sector
+  // max CHS has lba = 16450560 = 1024*255*63
+  if ((m_relativeSectors + m_totalSectors) <= 16450560) {
+    // FAT32 with CHS and LBA
+    m_partType = 0X0B;
+  } else {
+    // FAT32 with only LBA
+    m_partType = 0X0C;
+  }
+  if (!writeMbr()) {
+    return false;
+  }
+  initPbs();
+  setLe32(pbs->bpb.bpb32.sectorsPerFat32, m_fatSize);
+  setLe32(pbs->bpb.bpb32.fat32RootCluster, 2);
+  setLe16(pbs->bpb.bpb32.fat32FSInfoSector, 1);
+  setLe16(pbs->bpb.bpb32.fat32BackBootSector, 6);
+  pbs->bpb.bpb32.physicalDriveNumber = 0X80;
+  pbs->bpb.bpb32.extSignature = EXTENDED_BOOT_SIGNATURE;
+  setLe32(pbs->bpb.bpb32.volumeSerialNumber, 1234567);
+  for (size_t i = 0; i < sizeof(pbs->bpb.bpb32.volumeLabel); i++) {
+    pbs->bpb.bpb32.volumeLabel[i] = ' ';
+  }
+  pbs->bpb.bpb32.volumeType[0] = 'F';
+  pbs->bpb.bpb32.volumeType[1] = 'A';
+  pbs->bpb.bpb32.volumeType[2] = 'T';
+  pbs->bpb.bpb32.volumeType[3] = '3';
+  pbs->bpb.bpb32.volumeType[4] = '2';
+  if (!m_dev->writeSector(m_relativeSectors, m_secBuf)  ||
+      !m_dev->writeSector(m_relativeSectors + 6, m_secBuf)) {
+    return false;
+  }
+  // write extra boot area and backup
+  memset(m_secBuf, 0 , BYTES_PER_SECTOR);
+  setLe32(fsi->trailSignature, FSINFO_TRAIL_SIGNATURE);
+  if (!m_dev->writeSector(m_relativeSectors + 2, m_secBuf)  ||
+      !m_dev->writeSector(m_relativeSectors + 8, m_secBuf)) {
+    return false;
+  }
+  // write FSINFO sector and backup
+  setLe32(fsi->leadSignature, FSINFO_LEAD_SIGNATURE);
+  setLe32(fsi->structSignature, FSINFO_STRUCT_SIGNATURE);
+  setLe32(fsi->freeCount, 0XFFFFFFFF);
+  setLe32(fsi->nextFree, 0XFFFFFFFF);
+  if (!m_dev->writeSector(m_relativeSectors + 1, m_secBuf)  ||
+      !m_dev->writeSector(m_relativeSectors + 7, m_secBuf)) {
+    return false;
+  }
+  return initFatDir(32, 2*m_fatSize + m_sectorsPerCluster);
+}
+//------------------------------------------------------------------------------
+bool FatFormatter::writeMbr() {
+  memset(m_secBuf, 0, BYTES_PER_SECTOR);
+  MbrSector_t* mbr = reinterpret_cast<MbrSector_t*>(m_secBuf);
+
+#if USE_LBA_TO_CHS
+  lbaToMbrChs(mbr->part->beginCHS, m_capacityMB, m_relativeSectors);
+  lbaToMbrChs(mbr->part->endCHS, m_capacityMB,
+              m_relativeSectors + m_totalSectors -1);
+#else  // USE_LBA_TO_CHS
+  mbr->part->beginCHS[0] = 1;
+  mbr->part->beginCHS[1] = 1;
+  mbr->part->beginCHS[2] = 0;
+  mbr->part->endCHS[0] = 0XFE;
+  mbr->part->endCHS[1] = 0XFF;
+  mbr->part->endCHS[2] = 0XFF;
+#endif  // USE_LBA_TO_CHS
+
+  mbr->part->type = m_partType;
+  setLe32(mbr->part->relativeSectors, m_relativeSectors);
+  setLe32(mbr->part->totalSectors, m_totalSectors);
+  setLe16(mbr->signature, MBR_SIGNATURE);
+  return m_dev->writeSector(0, m_secBuf);
+}

+ 66 - 0
lib/SdFat_NoArduino/src/FatLib/FatFormatter.h

@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FatFormatter_h
+#define FatFormatter_h
+#include "../common/SysCall.h"
+#include "../common/FsBlockDevice.h"
+/**
+ * \class FatFormatter
+ * \brief Format a FAT volume.
+ */
+class FatFormatter {
+ public:
+  /**
+   * Format a FAT volume.
+   *
+   * \param[in] dev Block device for volume.
+   * \param[in] secBuffer buffer for writing to volume.
+   * \param[in] pr Print device for progress output.
+   *
+   * \return true for success or false for failure.
+   */
+  bool format(FsBlockDevice* dev, uint8_t* secBuffer, print_t* pr = nullptr);
+
+ private:
+  bool initFatDir(uint8_t fatType, uint32_t sectorCount);
+  void initPbs();
+  bool makeFat16();
+  bool makeFat32();
+  bool writeMbr();
+  uint32_t m_capacityMB;
+  uint32_t m_dataStart;
+  uint32_t m_fatSize;
+  uint32_t m_fatStart;
+  uint32_t m_relativeSectors;
+  uint32_t m_sectorCount;
+  uint32_t m_totalSectors;
+  FsBlockDevice* m_dev;
+  print_t* m_pr;
+  uint8_t* m_secBuf;
+  uint16_t m_reservedSectorCount;
+  uint8_t m_partType;
+  uint8_t m_sectorsPerCluster;
+};
+#endif  // FatFormatter_h

+ 29 - 0
lib/SdFat_NoArduino/src/FatLib/FatLib.h

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FatLib_h
+#define FatLib_h
+#include "FatVolume.h"
+#include "FatFormatter.h"
+#endif  // FatLib_h

+ 356 - 0
lib/SdFat_NoArduino/src/FatLib/FatName.cpp

@@ -0,0 +1,356 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "FatName.cpp"
+#include "../common/DebugMacros.h"
+#include "../common/FsUtf.h"
+#include "FatLib.h"
+//------------------------------------------------------------------------------
+uint16_t FatFile::getLfnChar(DirLfn_t* ldir, uint8_t i) {
+  if (i < 5) {
+    return getLe16(ldir->unicode1 + 2*i);
+  } else if (i < 11) {
+    return getLe16(ldir->unicode2 + 2*i - 10);
+  } else if (i < 13) {
+    return getLe16(ldir->unicode3 + 2*i - 22);
+  }
+  DBG_HALT_IF(i >= 13);
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t FatFile::getName(char* name, size_t size) {
+#if !USE_LONG_FILE_NAMES
+  return getSFN(name, size);
+#elif USE_UTF8_LONG_NAMES
+  return getName8(name, size);
+#else
+  return getName7(name, size);
+#endif  // !USE_LONG_FILE_NAMES
+}
+//------------------------------------------------------------------------------
+size_t FatFile::getName7(char* name, size_t size) {
+  FatFile dir;
+  DirLfn_t* ldir;
+  size_t n = 0;
+  if (!isOpen()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (!isLFN()) {
+    return getSFN(name, size);
+  }
+  if (!dir.openCluster(this)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  for (uint8_t order = 1; order <= m_lfnOrd; order++) {
+    ldir = reinterpret_cast<DirLfn_t*>(dir.cacheDir(m_dirIndex - order));
+    if (!ldir) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (ldir->attributes != FAT_ATTRIB_LONG_NAME ||
+        order != (ldir->order & 0X1F)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    for (uint8_t i = 0; i < 13; i++) {
+      uint16_t c = getLfnChar(ldir, i);
+      if (c == 0) {
+        goto done;
+      }
+      if ((n + 1) >= size) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      name[n++] = c >= 0X7F ? '?' : c;
+    }
+  }
+ done:
+  name[n] = 0;
+  return n;
+
+ fail:
+  name[0] = '\0';
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t FatFile::getName8(char* name, size_t size) {
+  char* end = name + size;
+  char* str = name;
+  char* ptr;
+  FatFile dir;
+  DirLfn_t* ldir;
+  uint16_t hs = 0;
+  uint32_t cp;
+  if (!isOpen()) {
+      DBG_FAIL_MACRO;
+      goto fail;
+  }
+  if (!isLFN()) {
+    return getSFN(name, size);
+  }
+  if (!dir.openCluster(this)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  for (uint8_t order = 1; order <= m_lfnOrd; order++) {
+    ldir = reinterpret_cast<DirLfn_t*>(dir.cacheDir(m_dirIndex - order));
+    if (!ldir) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (ldir->attributes != FAT_ATTRIB_LONG_NAME ||
+        order != (ldir->order & 0X1F)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    for (uint8_t i = 0; i < 13; i++) {
+      uint16_t c = getLfnChar(ldir, i);
+      if (hs) {
+        if (!FsUtf::isLowSurrogate(c)) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+        cp = FsUtf::u16ToCp(hs, c);
+        hs = 0;
+      } else if (!FsUtf::isSurrogate(c)) {
+        if (c == 0) {
+          goto done;
+        }
+        cp = c;
+      } else if (FsUtf::isHighSurrogate(c)) {
+        hs = c;
+        continue;
+      } else {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      // Save space for zero byte.
+      ptr = FsUtf::cpToMb(cp, str, end - 1);
+      if (!ptr) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      str = ptr;
+    }
+  }
+ done:
+  *str = '\0';
+  return str - name;
+
+ fail:
+  *name = 0;
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t FatFile::getSFN(char* name, size_t size) {
+  char c;
+  uint8_t j = 0;
+  uint8_t lcBit = FAT_CASE_LC_BASE;
+  uint8_t* ptr;
+  DirFat_t* dir;
+  if (!isOpen()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (isRoot()) {
+    if (size < 2) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    name[0] = '/';
+    name[1] = '\0';
+    return 1;
+  }
+  // cache entry
+  dir = cacheDirEntry(FsCache::CACHE_FOR_READ);
+  if (!dir) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  ptr = dir->name;
+  // format name
+  for (uint8_t i = 0; i < 12; i++) {
+    if (i == 8) {
+      if (*ptr == ' ') {
+        break;
+      }
+      lcBit = FAT_CASE_LC_EXT;
+      c = '.';
+    } else {
+      c = *ptr++;
+      if ('A' <= c && c <= 'Z' && (lcBit & dir->caseFlags)) {
+        c += 'a' - 'A';
+      }
+      if (c == ' ') {
+        continue;
+      }
+    }
+    if ((j + 1u) >= size) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    name[j++] = c;
+  }
+  name[j] = '\0';
+  return j;
+
+ fail:
+  name[0] = '\0';
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t FatFile::printName(print_t* pr) {
+#if !USE_LONG_FILE_NAMES
+  return printSFN(pr);
+#elif USE_UTF8_LONG_NAMES
+  return printName8(pr);
+# else  // USE_LONG_FILE_NAMES
+  return printName7(pr);
+#endif  // !USE_LONG_FILE_NAMES
+  }
+//------------------------------------------------------------------------------
+size_t FatFile::printName7(print_t* pr) {
+  FatFile dir;
+  DirLfn_t* ldir;
+  size_t n = 0;
+  uint8_t buf[13];
+  uint8_t i;
+
+  if (!isOpen()) {
+      DBG_FAIL_MACRO;
+      goto fail;
+  }
+  if (!isLFN()) {
+    return printSFN(pr);
+  }
+  if (!dir.openCluster(this)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  for (uint8_t order = 1; order <= m_lfnOrd; order++) {
+    ldir = reinterpret_cast<DirLfn_t*>(dir.cacheDir(m_dirIndex - order));
+    if (!ldir) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (ldir->attributes != FAT_ATTRIB_LONG_NAME ||
+        order != (ldir->order & 0X1F)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    for (i = 0; i < 13; i++) {
+      uint16_t u = getLfnChar(ldir, i);
+      if (u == 0) {
+        // End of name.
+        break;
+      }
+      buf[i] = u < 0X7F ? u : '?';
+      n++;
+    }
+    pr->write(buf, i);
+  }
+  return n;
+
+ fail:
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t FatFile::printName8(print_t *pr) {
+  FatFile dir;
+  DirLfn_t* ldir;
+  uint16_t hs = 0;
+  uint32_t cp;
+  size_t n = 0;
+  char buf[5];
+  char* end = buf + sizeof(buf);
+  if (!isOpen()) {
+      DBG_FAIL_MACRO;
+      goto fail;
+  }
+  if (!isLFN()) {
+    return printSFN(pr);
+  }
+  if (!dir.openCluster(this)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  for (uint8_t order = 1; order <= m_lfnOrd; order++) {
+    ldir = reinterpret_cast<DirLfn_t*>(dir.cacheDir(m_dirIndex - order));
+    if (!ldir) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (ldir->attributes != FAT_ATTRIB_LONG_NAME ||
+        order != (ldir->order & 0X1F)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    for (uint8_t i = 0; i < 13; i++) {
+      uint16_t c = getLfnChar(ldir, i);;
+      if (hs) {
+        if (!FsUtf::isLowSurrogate(c)) {
+          DBG_FAIL_MACRO;
+          goto fail;
+        }
+        cp = FsUtf::u16ToCp(hs, c);
+        hs = 0;
+      } else if (!FsUtf::isSurrogate(c)) {
+        if (c == 0) {
+          break;
+        }
+        cp = c;
+      } else if (FsUtf::isHighSurrogate(c)) {
+        hs = c;
+        continue;
+      } else {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      char* str = FsUtf::cpToMb(cp, buf, end);
+      if (!str) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      n += pr->write(buf, str - buf);
+    }
+  }
+  return n;
+
+ fail:
+  return 0;
+}
+//------------------------------------------------------------------------------
+size_t FatFile::printSFN(print_t* pr) {
+  char name[13];
+  if (!getSFN(name, sizeof(name))) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  return pr->write(name);
+
+ fail:
+  return 0;
+}

+ 489 - 0
lib/SdFat_NoArduino/src/FatLib/FatPartition.cpp

@@ -0,0 +1,489 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 <string.h>
+#define DBG_FILE "FatPartition.cpp"
+#include "../common/DebugMacros.h"
+#include "../common/PartitionTable.h"
+#include "FatLib.h"
+//------------------------------------------------------------------------------
+bool FatPartition::allocateCluster(uint32_t current, uint32_t* next) {
+  uint32_t find;
+  bool setStart;
+  if (m_allocSearchStart < current) {
+    // Try to keep file contiguous. Start just after current cluster.
+    find = current;
+    setStart = false;
+  } else {
+    find = m_allocSearchStart;
+    setStart = true;
+  }
+  while (1) {
+    find++;
+    if (find > m_lastCluster) {
+      if (setStart) {
+        // Can't find space, checked all clusters.
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      find = m_allocSearchStart;
+      setStart = true;
+      continue;
+    }
+    if (find == current) {
+      // Can't find space, already searched clusters after current.
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    uint32_t f;
+    int8_t fg = fatGet(find, &f);
+    if (fg < 0) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (fg && f == 0) {
+      break;
+    }
+  }
+  if (setStart) {
+    m_allocSearchStart = find;
+  }
+  // Mark end of chain.
+  if (!fatPutEOC(find)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (current) {
+    // Link clusters.
+    if (!fatPut(current, find)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+  updateFreeClusterCount(-1);
+  *next = find;
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+// find a contiguous group of clusters
+bool FatPartition::allocContiguous(uint32_t count, uint32_t* firstCluster) {
+  // flag to save place to start next search
+  bool setStart = true;
+  // start of group
+  uint32_t bgnCluster;
+  // end of group
+  uint32_t endCluster;
+  // Start at cluster after last allocated cluster.
+  endCluster = bgnCluster = m_allocSearchStart + 1;
+
+  // search the FAT for free clusters
+  while (1) {
+    if (endCluster > m_lastCluster) {
+      // Can't find space.
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    uint32_t f;
+    int8_t fg = fatGet(endCluster, &f);
+    if (fg < 0) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (f || fg == 0) {
+      // don't update search start if unallocated clusters before endCluster.
+      if (bgnCluster != endCluster) {
+        setStart = false;
+      }
+      // cluster in use try next cluster as bgnCluster
+      bgnCluster = endCluster + 1;
+    } else if ((endCluster - bgnCluster + 1) == count) {
+      // done - found space
+      break;
+    }
+    endCluster++;
+  }
+  // Remember possible next free cluster.
+  if (setStart) {
+    m_allocSearchStart = endCluster;
+  }
+  // mark end of chain
+  if (!fatPutEOC(endCluster)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  // link clusters
+  while (endCluster > bgnCluster) {
+    if (!fatPut(endCluster - 1, endCluster)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    endCluster--;
+  }
+  // Maintain count of free clusters.
+  updateFreeClusterCount(-count);
+
+  // return first cluster number to caller
+  *firstCluster = bgnCluster;
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+// Fetch a FAT entry - return -1 error, 0 EOC, else 1.
+int8_t FatPartition::fatGet(uint32_t cluster, uint32_t* value) {
+  uint32_t sector;
+  uint32_t next;
+  uint8_t* pc;
+
+  // error if reserved cluster of beyond FAT
+  if (cluster < 2 || cluster > m_lastCluster) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+
+  if (fatType() == 32) {
+    sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 2));
+    pc = fatCachePrepare(sector, FsCache::CACHE_FOR_READ);
+    if (!pc) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    uint16_t offset = (cluster << 2) & m_sectorMask;
+    next = getLe32(pc + offset);
+  } else if (fatType() == 16) {
+    cluster &= 0XFFFF;
+    sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 1) );
+    pc = fatCachePrepare(sector, FsCache::CACHE_FOR_READ);
+    if (!pc) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    uint16_t offset = (cluster << 1) & m_sectorMask;
+    next = getLe16(pc + offset);
+  } else if (FAT12_SUPPORT && fatType() == 12) {
+    uint16_t index = cluster;
+    index += index >> 1;
+    sector = m_fatStartSector + (index >> m_bytesPerSectorShift);
+    pc = fatCachePrepare(sector, FsCache::CACHE_FOR_READ);
+    if (!pc) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    index &= m_sectorMask;
+    uint16_t tmp = pc[index];
+    index++;
+    if (index == m_bytesPerSector) {
+      pc = fatCachePrepare(sector + 1, FsCache::CACHE_FOR_READ);
+      if (!pc) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      index = 0;
+    }
+    tmp |= pc[index] << 8;
+    next = cluster & 1 ? tmp >> 4 : tmp & 0XFFF;
+  } else {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (isEOC(next)) {
+    return 0;
+  }
+  *value = next;
+  return 1;
+
+ fail:
+  return -1;
+}
+//------------------------------------------------------------------------------
+// Store a FAT entry
+bool FatPartition::fatPut(uint32_t cluster, uint32_t value) {
+  uint32_t sector;
+  uint8_t* pc;
+
+  // error if reserved cluster of beyond FAT
+  if (cluster < 2 || cluster > m_lastCluster) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+
+  if (fatType() == 32) {
+    sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 2));
+    pc = fatCachePrepare(sector, FsCache::CACHE_FOR_WRITE);
+    if (!pc) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    uint16_t offset = (cluster << 2) & m_sectorMask;
+    setLe32(pc + offset, value);
+    return true;
+  }
+
+  if (fatType() == 16) {
+    cluster &= 0XFFFF;
+    sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 1) );
+    pc = fatCachePrepare(sector, FsCache::CACHE_FOR_WRITE);
+    if (!pc) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    uint16_t offset = (cluster << 1) & m_sectorMask;
+    setLe16(pc + offset, value);
+    return true;
+  }
+
+  if (FAT12_SUPPORT && fatType() == 12) {
+    uint16_t index = cluster;
+    index += index >> 1;
+    sector = m_fatStartSector + (index >> m_bytesPerSectorShift);
+    pc = fatCachePrepare(sector, FsCache::CACHE_FOR_WRITE);
+    if (!pc) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    index &= m_sectorMask;
+    uint8_t tmp = value;
+    if (cluster & 1) {
+      tmp = (pc[index] & 0XF) | tmp << 4;
+    }
+    pc[index] = tmp;
+
+    index++;
+    if (index == m_bytesPerSector) {
+      sector++;
+      index = 0;
+      pc = fatCachePrepare(sector, FsCache::CACHE_FOR_WRITE);
+      if (!pc) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+    tmp = value >> 4;
+    if (!(cluster & 1)) {
+      tmp = ((pc[index] & 0XF0)) | tmp >> 4;
+    }
+    pc[index] = tmp;
+    return true;
+  } else {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+// free a cluster chain
+bool FatPartition::freeChain(uint32_t cluster) {
+  uint32_t next;
+  int8_t fg;
+  do {
+    fg = fatGet(cluster, &next);
+    if (fg < 0) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    // free cluster
+    if (!fatPut(cluster, 0)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    // Add one to count of free clusters.
+    updateFreeClusterCount(1);
+    if (cluster < m_allocSearchStart) {
+      m_allocSearchStart = cluster - 1;
+    }
+    cluster = next;
+  } while (fg);
+
+  return true;
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+int32_t FatPartition::freeClusterCount() {
+#if MAINTAIN_FREE_CLUSTER_COUNT
+  if (m_freeClusterCount >= 0) {
+    return m_freeClusterCount;
+  }
+#endif  // MAINTAIN_FREE_CLUSTER_COUNT
+  uint32_t free = 0;
+  uint32_t sector;
+  uint32_t todo = m_lastCluster + 1;
+  uint16_t n;
+
+  if (FAT12_SUPPORT && fatType() == 12) {
+    for (unsigned i = 2; i < todo; i++) {
+      uint32_t c;
+      int8_t fg = fatGet(i, &c);
+      if (fg < 0) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      if (fg && c == 0) {
+        free++;
+      }
+    }
+  } else if (fatType() == 16 || fatType() == 32) {
+    sector = m_fatStartSector;
+    while (todo) {
+      uint8_t* pc = fatCachePrepare(sector++, FsCache::CACHE_FOR_READ);
+      if (!pc) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+      n =  fatType() == 16 ? m_bytesPerSector/2 : m_bytesPerSector/4;
+      if (todo < n) {
+        n = todo;
+      }
+      if (fatType() == 16) {
+        uint16_t* p16 = reinterpret_cast<uint16_t*>(pc);
+        for (uint16_t i = 0; i < n; i++) {
+          if (p16[i] == 0) {
+            free++;
+          }
+        }
+      } else {
+        uint32_t* p32 = reinterpret_cast<uint32_t*>(pc);
+        for (uint16_t i = 0; i < n; i++) {
+          if (p32[i] == 0) {
+            free++;
+          }
+        }
+      }
+      todo -= n;
+    }
+  } else {
+    // invalid FAT type
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  setFreeClusterCount(free);
+  return free;
+
+ fail:
+  return -1;
+}
+//------------------------------------------------------------------------------
+bool FatPartition::init(FsBlockDevice* dev, uint8_t part, uint32_t volStart) {
+  uint32_t clusterCount;
+  uint32_t totalSectors;
+  m_blockDev = dev;
+  pbs_t* pbs;
+  BpbFat32_t* bpb;
+  uint8_t tmp;
+  m_fatType = 0;
+  m_allocSearchStart = 1;
+  m_cache.init(dev);
+#if USE_SEPARATE_FAT_CACHE
+  m_fatCache.init(dev);
+#endif  // USE_SEPARATE_FAT_CACHE
+  // if part == 0 assume super floppy with FAT boot sector in sector zero
+  // if part > 0 read MBR/GPT partition table
+  if (part) {
+    volStart = partitionTableGetVolumeStartSector(m_cache, part);
+
+    if (!volStart) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  }
+  pbs = reinterpret_cast<pbs_t*>
+        (dataCachePrepare(volStart, FsCache::CACHE_FOR_READ));
+  if (!pbs) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  bpb = reinterpret_cast<BpbFat32_t*>(pbs->bpb);
+  if (bpb->fatCount != 2 || getLe16(bpb->bytesPerSector) != m_bytesPerSector) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  m_sectorsPerCluster = bpb->sectorsPerCluster;
+  m_clusterSectorMask = m_sectorsPerCluster - 1;
+  // determine shift that is same as multiply by m_sectorsPerCluster
+  m_sectorsPerClusterShift = 0;
+  for (tmp = 1; m_sectorsPerCluster != tmp; tmp <<= 1) {
+    if (tmp == 0) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    m_sectorsPerClusterShift++;
+  }
+  m_sectorsPerFat = getLe16(bpb->sectorsPerFat16);
+  if (m_sectorsPerFat == 0) {
+    m_sectorsPerFat = getLe32(bpb->sectorsPerFat32);
+  }
+  m_fatStartSector = volStart + getLe16(bpb->reservedSectorCount);
+
+  // count for FAT16 zero for FAT32
+  m_rootDirEntryCount = getLe16(bpb->rootDirEntryCount);
+
+  // directory start for FAT16 dataStart for FAT32
+  m_rootDirStart = m_fatStartSector + 2 * m_sectorsPerFat;
+  // data start for FAT16 and FAT32
+  m_dataStartSector = m_rootDirStart +
+    ((FS_DIR_SIZE*m_rootDirEntryCount + m_bytesPerSector - 1)/m_bytesPerSector);
+
+  // total sectors for FAT16 or FAT32
+  totalSectors = getLe16(bpb->totalSectors16);
+  if (totalSectors == 0) {
+    totalSectors = getLe32(bpb->totalSectors32);
+  }
+  // total data sectors
+  clusterCount = totalSectors - (m_dataStartSector - volStart);
+
+  // divide by cluster size to get cluster count
+  clusterCount >>= m_sectorsPerClusterShift;
+  m_lastCluster = clusterCount + 1;
+
+  // Indicate unknown number of free clusters.
+  setFreeClusterCount(-1);
+  // FAT type is determined by cluster count
+  if (clusterCount < 4085) {
+    m_fatType = 12;
+    if (!FAT12_SUPPORT) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+  } else if (clusterCount < 65525) {
+    m_fatType = 16;
+  } else {
+    m_rootDirStart = getLe32(bpb->fat32RootCluster);
+    m_fatType = 32;
+  }
+  m_cache.setMirrorOffset(m_sectorsPerFat);
+#if USE_SEPARATE_FAT_CACHE
+  m_fatCache.setMirrorOffset(m_sectorsPerFat);
+#endif  // USE_SEPARATE_FAT_CACHE
+  return true;
+
+ fail:
+  return false;
+}

+ 295 - 0
lib/SdFat_NoArduino/src/FatLib/FatPartition.h

@@ -0,0 +1,295 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FatPartition_h
+#define FatPartition_h
+/**
+ * \file
+ * \brief FatPartition class
+ */
+#include <stddef.h>
+#include "../common/SysCall.h"
+#include "../common/FsBlockDevice.h"
+#include "../common/FsCache.h"
+#include "../common/FsStructs.h"
+
+/** Type for FAT12 partition */
+const uint8_t FAT_TYPE_FAT12 = 12;
+
+/** Type for FAT12 partition */
+const uint8_t FAT_TYPE_FAT16 = 16;
+
+/** Type for FAT12 partition */
+const uint8_t FAT_TYPE_FAT32 = 32;
+
+//==============================================================================
+/**
+ * \class FatPartition
+ * \brief Access FAT16 and FAT32 partitions on raw file devices.
+ */
+class FatPartition {
+ public:
+  /** Create an instance of FatPartition
+   */
+  FatPartition() {}
+
+  /** \return The shift count required to multiply by bytesPerCluster. */
+  uint8_t bytesPerClusterShift() const {
+    return m_sectorsPerClusterShift + m_bytesPerSectorShift;
+  }
+  /** \return Number of bytes in a cluster. */
+  uint16_t bytesPerCluster() const {
+    return m_bytesPerSector << m_sectorsPerClusterShift;
+  }
+  /** \return Number of bytes per sector. */
+  uint16_t bytesPerSector() const {
+    return m_bytesPerSector;
+  }
+  /** \return The shift count required to multiply by bytesPerCluster. */
+  uint8_t bytesPerSectorShift() const {
+    return m_bytesPerSectorShift;
+  }
+  /** \return Number of directory entries per sector. */
+  uint16_t dirEntriesPerCluster() const {
+    return m_sectorsPerCluster*(m_bytesPerSector/FS_DIR_SIZE);
+  }
+  /** \return Mask for sector offset. */
+  uint16_t sectorMask() const {
+    return m_sectorMask;
+  }
+  /** \return The volume's cluster size in sectors. */
+  uint8_t sectorsPerCluster() const {
+    return m_sectorsPerCluster;
+  }
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+  uint8_t __attribute__((error("use sectorsPerCluster()"))) blocksPerCluster();
+#endif  // DOXYGEN_SHOULD_SKIP_THIS
+  /** \return The number of sectors in one FAT. */
+  uint32_t sectorsPerFat()  const {
+    return m_sectorsPerFat;
+  }
+  /** Clear the cache and returns a pointer to the cache.  Not for normal apps.
+   * \return A pointer to the cache buffer or zero if an error occurs.
+   */
+  uint8_t* cacheClear() {
+    return m_cache.clear();
+  }
+  /** \return The total number of clusters in the volume. */
+  uint32_t clusterCount() const {
+    return m_lastCluster - 1;
+  }
+  /** \return The shift count required to multiply by sectorsPerCluster. */
+  uint8_t sectorsPerClusterShift() const {
+    return m_sectorsPerClusterShift;
+  }
+  /** \return The logical sector number for the start of file data. */
+  uint32_t dataStartSector() const {
+    return m_dataStartSector;
+  }
+  /** End access to volume
+   * \return pointer to sector size buffer for format.
+   */
+  uint8_t* end() {
+    m_fatType = 0;
+    return cacheClear();
+  }
+  /** \return The number of File Allocation Tables. */
+  uint8_t fatCount() const {
+    return 2;
+  }
+  /** \return The logical sector number for the start of the first FAT. */
+  uint32_t fatStartSector() const {
+    return m_fatStartSector;
+  }
+  /** \return The FAT type of the volume. Values are 12, 16 or 32. */
+  uint8_t fatType() const {
+    return m_fatType;
+  }
+  /** \return free cluster count or -1 if an error occurs. */
+  int32_t freeClusterCount();
+  /** Initialize a FAT partition.
+   *
+   * \param[in] dev FsBlockDevice for this partition.
+   * \param[in] part The partition to be used.  Legal values for \a part are
+   * 1-4 to use the corresponding partition on a device formatted with
+   * a MBR, Master Boot Record, or zero if the device is formatted as
+   * a super floppy with the FAT boot sector in sector volStart.
+   * \param[in] volStart location of volume if part is zero.
+   *
+   * \return true for success or false for failure.
+   */
+  bool init(FsBlockDevice* dev, uint8_t part = 1, uint32_t volStart = 0);
+  /** \return The number of entries in the root directory for FAT16 volumes. */
+  uint16_t rootDirEntryCount() const {
+    return m_rootDirEntryCount;
+  }
+  /** \return The logical sector number for the start of the root directory
+       on FAT16 volumes or the first cluster number on FAT32 volumes. */
+  uint32_t rootDirStart() const {
+    return m_rootDirStart;
+  }
+  /** \return The number of sectors in the volume */
+  uint32_t volumeSectorCount() const {
+    return sectorsPerCluster()*clusterCount();
+  }
+  /** Debug access to FAT table
+   *
+   * \param[in] n cluster number.
+   * \param[out] v value of entry
+   * \return -1 error, 0 EOC, else 1.
+   */
+  int8_t dbgFat(uint32_t n, uint32_t* v) {
+    return fatGet(n, v);
+  }
+  /**
+   * Check for FsBlockDevice busy.
+   *
+   * \return true if busy else false.
+   */
+  bool isBusy() {return m_blockDev->isBusy();}
+  //----------------------------------------------------------------------------
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+  bool dmpDirSector(print_t* pr, uint32_t sector);
+  void dmpFat(print_t* pr, uint32_t start, uint32_t count);
+  bool dmpRootDir(print_t* pr, uint32_t n = 0);
+  void dmpSector(print_t* pr, uint32_t sector, uint8_t bits = 8);
+#endif  // DOXYGEN_SHOULD_SKIP_THIS
+  //----------------------------------------------------------------------------
+ private:
+  /** FatFile allowed access to private members. */
+  friend class FatFile;
+  //----------------------------------------------------------------------------
+  static const uint8_t  m_bytesPerSectorShift = 9;
+  static const uint16_t m_bytesPerSector = 1 << m_bytesPerSectorShift;
+  static const uint16_t m_sectorMask = m_bytesPerSector - 1;
+  //----------------------------------------------------------------------------
+  FsBlockDevice* m_blockDev;            // sector device
+  uint8_t  m_sectorsPerCluster;       // Cluster size in sectors.
+  uint8_t  m_clusterSectorMask;       // Mask to extract sector of cluster.
+  uint8_t  m_sectorsPerClusterShift;  // Cluster count to sector count shift.
+  uint8_t  m_fatType = 0;             // Volume type (12, 16, OR 32).
+  uint16_t m_rootDirEntryCount;       // Number of entries in FAT16 root dir.
+  uint32_t m_allocSearchStart;        // Start cluster for alloc search.
+  uint32_t m_sectorsPerFat;           // FAT size in sectors
+  uint32_t m_dataStartSector;         // First data sector number.
+  uint32_t m_fatStartSector;          // Start sector for first FAT.
+  uint32_t m_lastCluster;             // Last cluster number in FAT.
+  uint32_t m_rootDirStart;            // Start sector FAT16, cluster FAT32.
+  //----------------------------------------------------------------------------
+  // sector I/O functions.
+  bool cacheSafeRead(uint32_t sector, uint8_t* dst) {
+    return m_cache.cacheSafeRead(sector, dst);
+  }
+  bool cacheSafeRead(uint32_t sector, uint8_t* dst, size_t count) {
+    return m_cache.cacheSafeRead(sector, dst, count);
+  }
+  bool cacheSafeWrite(uint32_t sector, const uint8_t* dst) {
+    return m_cache.cacheSafeWrite(sector, dst);
+  }
+  bool cacheSafeWrite(uint32_t sector, const uint8_t* dst, size_t count) {
+    return m_cache.cacheSafeWrite(sector, dst, count);
+  }
+  bool syncDevice() {
+    return m_blockDev->syncDevice();
+  }
+#if MAINTAIN_FREE_CLUSTER_COUNT
+  int32_t  m_freeClusterCount;     // Count of free clusters in volume.
+  void setFreeClusterCount(int32_t value) {
+    m_freeClusterCount = value;
+  }
+  void updateFreeClusterCount(int32_t change) {
+    if (m_freeClusterCount >= 0) {
+      m_freeClusterCount += change;
+    }
+  }
+#else  // MAINTAIN_FREE_CLUSTER_COUNT
+  void setFreeClusterCount(int32_t value) {
+    (void)value;
+  }
+  void updateFreeClusterCount(int32_t change) {
+    (void)change;
+  }
+#endif  // MAINTAIN_FREE_CLUSTER_COUNT
+// sector caches
+  FsCache m_cache;
+  bool cachePrepare(uint32_t sector, uint8_t option) {
+    return m_cache.prepare(sector, option);
+  }
+  FsCache* dataCache() {return &m_cache;}
+#if USE_SEPARATE_FAT_CACHE
+  FsCache m_fatCache;
+  uint8_t* fatCachePrepare(uint32_t sector, uint8_t options) {
+    options |= FsCache::CACHE_STATUS_MIRROR_FAT;
+    return m_fatCache.prepare(sector, options);
+  }
+  bool cacheSync() {
+    return m_cache.sync() && m_fatCache.sync() && syncDevice();
+  }
+#else  // USE_SEPARATE_FAT_CACHE
+  uint8_t* fatCachePrepare(uint32_t sector, uint8_t options) {
+    options |= FsCache::CACHE_STATUS_MIRROR_FAT;
+    return dataCachePrepare(sector, options);
+  }
+  bool cacheSync() {
+    return m_cache.sync() && syncDevice();
+  }
+#endif  // USE_SEPARATE_FAT_CACHE
+  uint8_t* dataCachePrepare(uint32_t sector, uint8_t options) {
+    return m_cache.prepare(sector, options);
+  }
+  void cacheInvalidate() {
+    m_cache.invalidate();
+  }
+  bool cacheSyncData() {
+    return m_cache.sync();
+  }
+  uint8_t* cacheAddress() {
+    return m_cache.cacheBuffer();
+  }
+  uint32_t cacheSectorNumber() {
+    return m_cache.sector();
+  }
+  void cacheDirty() {
+    m_cache.dirty();
+  }
+  //----------------------------------------------------------------------------
+  bool allocateCluster(uint32_t current, uint32_t* next);
+  bool allocContiguous(uint32_t count, uint32_t* firstCluster);
+  uint8_t sectorOfCluster(uint32_t position) const {
+    return (position >> 9) & m_clusterSectorMask;
+  }
+  uint32_t clusterStartSector(uint32_t cluster) const {
+    return m_dataStartSector + ((cluster - 2) << m_sectorsPerClusterShift);
+  }
+  int8_t fatGet(uint32_t cluster, uint32_t* value);
+  bool fatPut(uint32_t cluster, uint32_t value);
+  bool fatPutEOC(uint32_t cluster) {
+    return fatPut(cluster, 0x0FFFFFFF);
+  }
+  bool freeChain(uint32_t cluster);
+  bool isEOC(uint32_t cluster) const {
+    return cluster > m_lastCluster;
+  }
+};
+#endif  // FatPartition

+ 45 - 0
lib/SdFat_NoArduino/src/FatLib/FatVolume.cpp

@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "FatVolume.cpp"
+#include "../common/DebugMacros.h"
+#include "FatLib.h"
+FatVolume* FatVolume::m_cwv = nullptr;
+//------------------------------------------------------------------------------
+bool FatVolume::chdir(const char *path) {
+  FatFile dir;
+  if (!dir.open(vwd(), path, O_RDONLY)) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (!dir.isDir()) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  m_vwd = dir;
+  return true;
+
+ fail:
+  return false;
+}

+ 359 - 0
lib/SdFat_NoArduino/src/FatLib/FatVolume.h

@@ -0,0 +1,359 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FatVolume_h
+#define FatVolume_h
+#include "FatFile.h"
+/**
+ * \file
+ * \brief FatVolume class
+ */
+//------------------------------------------------------------------------------
+/**
+ * \class FatVolume
+ * \brief Integration class for the FatLib library.
+ */
+class FatVolume : public  FatPartition {
+ public:
+  /** Get file's user settable attributes.
+   * \param[in] path path to file.
+   * \return user settable file attributes for success else -1.
+   */
+  int attrib(const char* path) {
+    File32 tmpFile;
+    return tmpFile.open(this, path, O_RDONLY) ? tmpFile.attrib() : -1;
+  }
+  /** Set file's user settable attributes.
+   * \param[in] path path to file.
+   * \param[in] bits bit-wise or of selected attributes: FS_ATTRIB_READ_ONLY,
+   *            FS_ATTRIB_HIDDEN, FS_ATTRIB_SYSTEM, FS_ATTRIB_ARCHIVE.
+   *
+   * \return true for success or false for failure.
+   */
+  bool attrib(const char* path, uint8_t bits) {
+    File32 tmpFile;
+    return tmpFile.open(this, path, O_RDONLY) ? tmpFile.attrib(bits) : false;
+  }
+  /**
+   * Initialize an FatVolume object.
+   * \param[in] dev Device block driver.
+   * \param[in] setCwv Set current working volume if true.
+   * \param[in] part partition to initialize.
+   * \param[in] volStart Start sector of volume if part is zero.
+   * \return true for success or false for failure.
+   */
+  bool begin(FsBlockDevice* dev, bool setCwv = true,
+             uint8_t part = 1, uint32_t volStart = 0) {
+    if (!init(dev, part, volStart)) {
+      return false;
+    }
+    if (!chdir()) {
+      return false;
+    }
+    if (setCwv || !m_cwv) {
+      m_cwv = this;
+    }
+    return true;
+  }
+  /** Change global current working volume to this volume. */
+  void chvol() {m_cwv = this;}
+
+  /**
+   * Set volume working directory to root.
+   * \return true for success or false for failure.
+   */
+  bool chdir() {
+    m_vwd.close();
+    return m_vwd.openRoot(this);
+  }
+  /**
+   * Set volume working directory.
+   * \param[in] path Path for volume working directory.
+   * \return true for success or false for failure.
+   */
+  bool chdir(const char *path);
+  //----------------------------------------------------------------------------
+  /**
+   * Test for the existence of a file.
+   *
+   * \param[in] path Path of the file to be tested for.
+   *
+   * \return true if the file exists else false.
+   */
+  bool exists(const char* path) {
+    FatFile tmp;
+    return tmp.open(this, path, O_RDONLY);
+  }
+  //----------------------------------------------------------------------------
+  /** List the directory contents of the volume root directory.
+   *
+   * \param[in] pr Print stream for list.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(print_t* pr, uint8_t flags = 0) {
+    return m_vwd.ls(pr, flags);
+  }
+  //----------------------------------------------------------------------------
+  /** List the contents of a directory.
+   *
+   * \param[in] pr Print stream for list.
+   *
+   * \param[in] path directory to list.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(print_t* pr, const char* path, uint8_t flags) {
+    FatFile dir;
+    return dir.open(this, path, O_RDONLY) && dir.ls(pr, flags);
+  }
+  //----------------------------------------------------------------------------
+  /** Make a subdirectory in the volume root directory.
+   *
+   * \param[in] path A path with a valid name for the subdirectory.
+   *
+   * \param[in] pFlag Create missing parent directories if true.
+   *
+   * \return true for success or false for failure.
+   */
+  bool mkdir(const char* path, bool pFlag = true) {
+    FatFile sub;
+    return sub.mkdir(vwd(), path, pFlag);
+  }
+  //----------------------------------------------------------------------------
+  /** open a file
+   *
+   * \param[in] path location of file to be opened.
+   * \param[in] oflag open flags.
+   * \return a File32 object.
+   */
+  File32 open(const char *path, oflag_t oflag = O_RDONLY) {
+    File32 tmpFile;
+    tmpFile.open(this, path, oflag);
+    return tmpFile;
+  }
+  //----------------------------------------------------------------------------
+  /** Remove a file from the volume root directory.
+   *
+   * \param[in] path A path with a valid name for the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool remove(const char* path) {
+    FatFile tmp;
+    return tmp.open(this, path, O_WRONLY) && tmp.remove();
+  }
+  //----------------------------------------------------------------------------
+  /** Rename a file or subdirectory.
+   *
+   * \param[in] oldPath Path name to the file or subdirectory to be renamed.
+   *
+   * \param[in] newPath New path name of the file or subdirectory.
+   *
+   * The \a newPath object must not exist before the rename call.
+   *
+   * The file to be renamed must not be open.  The directory entry may be
+   * moved and file system corruption could occur if the file is accessed by
+   * a file object that was opened before the rename() call.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rename(const char *oldPath, const char *newPath) {
+    FatFile file;
+    return file.open(vwd(), oldPath, O_RDONLY) && file.rename(vwd(), newPath);
+  }
+  //----------------------------------------------------------------------------
+  /** Remove a subdirectory from the volume's working directory.
+   *
+   * \param[in] path A path with a valid name for the subdirectory.
+   *
+   * The subdirectory file will be removed only if it is empty.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rmdir(const char* path) {
+    FatFile sub;
+    return sub.open(this, path, O_RDONLY) && sub.rmdir();
+  }
+  //----------------------------------------------------------------------------
+  /** Truncate a file to a specified length.  The current file position
+   * will be at the new EOF.
+   *
+   * \param[in] path A path with a valid name for the file.
+   * \param[in] length The desired length for the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool truncate(const char* path, uint32_t length) {
+    FatFile file;
+    return file.open(this, path, O_WRONLY) && file.truncate(length);
+  }
+#if ENABLE_ARDUINO_SERIAL
+   /** List the directory contents of the root directory to Serial.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(uint8_t flags = 0) {
+    return ls(&Serial, flags);
+  }
+  /** List the directory contents of a directory to Serial.
+   *
+   * \param[in] path directory to list.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(const char* path, uint8_t flags = 0) {
+    return ls(&Serial, path, flags);
+  }
+#endif  // ENABLE_ARDUINO_SERIAL
+#if ENABLE_ARDUINO_STRING
+  //----------------------------------------------------------------------------
+  /**
+   * Set volume working directory.
+   * \param[in] path Path for volume working directory.
+   * \return true for success or false for failure.
+   */
+  bool chdir(const String& path) {
+    return chdir(path.c_str());
+  }
+   /**
+   * Test for the existence of a file.
+   *
+   * \param[in] path Path of the file to be tested for.
+   *
+   * \return true if the file exists else false.
+   */
+  bool exists(const String& path) {
+    return exists(path.c_str());
+  }
+  /** Make a subdirectory in the volume root directory.
+   *
+   * \param[in] path A path with a valid name for the subdirectory.
+   *
+   * \param[in] pFlag Create missing parent directories if true.
+   *
+   * \return true for success or false for failure.
+   */
+  bool mkdir(const String& path, bool pFlag = true) {
+    return mkdir(path.c_str(), pFlag);
+  }
+  /** open a file
+   *
+   * \param[in] path location of file to be opened.
+   * \param[in] oflag open flags.
+   * \return a File32 object.
+   */
+  File32 open(const String& path, oflag_t oflag = O_RDONLY) {
+    return open(path.c_str(), oflag );
+  }
+  /** Remove a file from the volume root directory.
+   *
+   * \param[in] path A path with a valid name for the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool remove(const String& path) {
+    return remove(path.c_str());
+  }
+  /** Rename a file or subdirectory.
+   *
+   * \param[in] oldPath Path name to the file or subdirectory to be renamed.
+   *
+   * \param[in] newPath New path name of the file or subdirectory.
+   *
+   * The \a newPath object must not exist before the rename call.
+   *
+   * The file to be renamed must not be open.  The directory entry may be
+   * moved and file system corruption could occur if the file is accessed by
+   * a file object that was opened before the rename() call.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rename(const String& oldPath, const String& newPath) {
+    return rename(oldPath.c_str(), newPath.c_str());
+  }
+  /** Remove a subdirectory from the volume's working directory.
+   *
+   * \param[in] path A path with a valid name for the subdirectory.
+   *
+   * The subdirectory file will be removed only if it is empty.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rmdir(const String& path) {
+    return rmdir(path.c_str());
+  }
+  /** Truncate a file to a specified length.  The current file position
+   * will be at the new EOF.
+   *
+   * \param[in] path A path with a valid name for the file.
+   * \param[in] length The desired length for the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool truncate(const String& path, uint32_t length) {
+    return truncate(path.c_str(), length);
+  }
+#endif  // ENABLE_ARDUINO_STRING
+
+ private:
+  friend FatFile;
+  static FatVolume* cwv() {return m_cwv;}
+  FatFile* vwd() {return &m_vwd;}
+  static FatVolume* m_cwv;
+  FatFile m_vwd;
+};
+#endif  // FatVolume_h

+ 80 - 0
lib/SdFat_NoArduino/src/FreeStack.cpp

@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define FREE_STACK_CPP
+#include "FreeStack.h"
+#if defined(HAS_UNUSED_STACK) && HAS_UNUSED_STACK
+//------------------------------------------------------------------------------
+inline char* stackBegin() {
+#if defined(__AVR__)
+  return __brkval ? __brkval : &__bss_end;
+#elif defined(__IMXRT1062__)
+  return reinterpret_cast<char*>(&_ebss);
+#elif defined(__arm__)
+  return reinterpret_cast<char*>(sbrk(0));
+#else  // defined(__AVR__)
+#error "undefined stackBegin"
+#endif  // defined(__AVR__)
+}
+//------------------------------------------------------------------------------
+inline char* stackPointer() {
+#if defined(__AVR__)
+  return reinterpret_cast<char*>(SP);
+#elif defined(__arm__)
+  register uint32_t sp asm("sp");
+  return reinterpret_cast<char*>(sp);
+#else  // defined(__AVR__)
+#error "undefined stackPointer"
+#endif  // defined(__AVR__)
+}
+//------------------------------------------------------------------------------
+/** Stack fill pattern. */
+const char FILL = 0x55;
+void FillStack() {
+  char* p = stackBegin();
+  char* top = stackPointer();
+  while (p < top) {
+    *p++ = FILL;
+  }
+}
+//------------------------------------------------------------------------------
+// May fail if malloc or new is used.
+int UnusedStack() {
+  char* h = stackBegin();
+  char* top = stackPointer();
+  int n;
+
+  for (n = 0; (h + n) < top; n++) {
+    if (h[n] != FILL) {
+      if (n >= 16) {
+        break;
+      }
+      // Attempt to skip used heap.
+      h += n;
+      n = 0;
+    }
+  }
+  return n;
+}
+#endif  // defined(HAS_UNUSED_STACK) && HAS_UNUSED_STACK

+ 94 - 0
lib/SdFat_NoArduino/src/FreeStack.h

@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FreeStack_h
+#define FreeStack_h
+/**
+ * \file
+ * \brief FreeStack() function.
+ */
+#include <stdint.h>
+#if defined(__AVR__) || defined(DOXYGEN)
+#include <avr/io.h>
+/** Indicate FillStack() and UnusedStack() are available. */
+#define HAS_UNUSED_STACK 1
+/** boundary between stack and heap. */
+extern char *__brkval;
+/** End of bss section.*/
+extern char __bss_end;
+/** Amount of free stack space.
+ * \return The number of free bytes.
+ */
+inline int FreeStack() {
+  char* sp = reinterpret_cast<char*>(SP);
+  return __brkval ? sp - __brkval : sp - &__bss_end;
+}
+#elif defined(ARDUINO_ARCH_APOLLO3)
+#define HAS_UNUSED_STACK 0
+#elif defined(PLATFORM_ID)  // Particle board
+#include "Arduino.h"
+inline int FreeStack() {
+  return System.freeMemory();
+}
+#elif defined(__IMXRT1062__)
+#define HAS_UNUSED_STACK 1
+extern uint8_t _ebss;
+inline int FreeStack() {
+  register uint32_t sp asm("sp");
+  return reinterpret_cast<char*>(sp) - reinterpret_cast<char*>(&_ebss);
+}
+#elif defined(__arm__)
+#define HAS_UNUSED_STACK 1
+extern "C" char* sbrk(int incr);
+inline int FreeStack() {
+  register uint32_t sp asm("sp");
+  return reinterpret_cast<char*>(sp) - reinterpret_cast<char*>(sbrk(0));
+}
+#else  // defined(__AVR__) || defined(DOXYGEN)
+#ifndef FREE_STACK_CPP
+#warning FreeStack is not defined for this system.
+#endif  // FREE_STACK_CPP
+inline int FreeStack() {
+  return 0;
+}
+#endif  // defined(__AVR__) || defined(DOXYGEN)
+#if defined(HAS_UNUSED_STACK) || defined(DOXYGEN)
+/** Fill stack with 0x55 pattern */
+void FillStack();
+/**
+ * Determine the amount of unused stack.
+ *
+ * FillStack() must be called to fill the stack with a 0x55 pattern.
+ *
+ * UnusedStack() may fail if malloc() or new is use.
+ *
+ * \return number of bytes with 0x55 pattern.
+ */
+int UnusedStack();
+#else  // HAS_UNUSED_STACK
+#define HAS_UNUSED_STACK 0
+inline void FillStack() {}
+inline int UnusedStack() {return 0;}
+#endif  // defined(HAS_UNUSED_STACK)
+#endif  // FreeStack_h

+ 219 - 0
lib/SdFat_NoArduino/src/FsLib/FsFile.cpp

@@ -0,0 +1,219 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "FsLib.h"
+//------------------------------------------------------------------------------
+FsBaseFile::FsBaseFile(const FsBaseFile& from) {
+  m_fFile = nullptr;
+  m_xFile = nullptr;
+  if (from.m_fFile) {
+    m_fFile = new (m_fileMem) FatFile;
+    *m_fFile = *from.m_fFile;
+  } else if (from.m_xFile) {
+    m_xFile = new (m_fileMem) ExFatFile;
+    *m_xFile = *from.m_xFile;
+  }
+}
+//------------------------------------------------------------------------------
+FsBaseFile& FsBaseFile::operator=(const FsBaseFile& from) {
+  if (this == &from) {return *this;}
+  close();
+  if (from.m_fFile) {
+    m_fFile = new (m_fileMem) FatFile;
+    *m_fFile = *from.m_fFile;
+  } else if (from.m_xFile) {
+    m_xFile = new (m_fileMem) ExFatFile;
+    *m_xFile = *from.m_xFile;
+  }
+  return *this;
+}
+//------------------------------------------------------------------------------
+bool FsBaseFile::close() {
+  bool rtn = m_fFile ? m_fFile->close() : m_xFile ? m_xFile->close() : true;
+  m_fFile = nullptr;
+  m_xFile = nullptr;
+  return rtn;
+}
+//------------------------------------------------------------------------------
+bool FsBaseFile::mkdir(FsBaseFile* dir, const char* path, bool pFlag) {
+  close();
+  if (dir->m_fFile) {
+    m_fFile = new (m_fileMem) FatFile;
+    if (m_fFile->mkdir(dir->m_fFile, path, pFlag)) {
+      return true;
+    }
+    m_fFile = nullptr;
+  } else if (dir->m_xFile) {
+    m_xFile = new (m_fileMem) ExFatFile;
+    if (m_xFile->mkdir(dir->m_xFile, path, pFlag)) {
+      return true;
+    }
+    m_xFile = nullptr;
+  }
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FsBaseFile::open(FsVolume* vol, const char* path, oflag_t oflag) {
+  if (!vol) {
+    return false;
+  }
+  close();
+  if (vol->m_fVol) {
+    m_fFile = new (m_fileMem) FatFile;
+    if (m_fFile && m_fFile->open(vol->m_fVol, path, oflag)) {
+      return true;
+    }
+    m_fFile = nullptr;
+  } else if (vol->m_xVol) {
+    m_xFile = new (m_fileMem) ExFatFile;
+    if (m_xFile && m_xFile->open(vol->m_xVol, path, oflag)) {
+      return true;
+    }
+    m_xFile = nullptr;
+  }
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FsBaseFile::open(FsBaseFile* dir, const char* path, oflag_t oflag) {
+  close();
+  if (dir->m_fFile) {
+    m_fFile = new (m_fileMem) FatFile;
+    if (m_fFile->open(dir->m_fFile, path, oflag)) {
+      return true;
+    }
+    m_fFile = nullptr;
+  } else if (dir->m_xFile) {
+    m_xFile = new (m_fileMem) ExFatFile;
+    if (m_xFile->open(dir->m_xFile, path, oflag)) {
+      return true;
+    }
+    m_xFile = nullptr;
+  }
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FsBaseFile::open(FsBaseFile* dir, uint32_t index, oflag_t oflag) {
+  close();
+  if (dir->m_fFile) {
+    m_fFile = new (m_fileMem) FatFile;
+    if (m_fFile->open(dir->m_fFile, index, oflag)) {
+      return true;
+    }
+    m_fFile = nullptr;
+  } else if (dir->m_xFile) {
+    m_xFile = new (m_fileMem) ExFatFile;
+    if (m_xFile->open(dir->m_xFile, index, oflag)) {
+      return true;
+    }
+    m_xFile = nullptr;
+  }
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FsBaseFile::openCwd() {
+  close();
+  if (FsVolume::m_cwv && FsVolume::m_cwv->m_fVol) {
+    m_fFile = new (m_fileMem) FatFile;
+    if (m_fFile->openCwd()) {
+      return true;
+    }
+    m_fFile = nullptr;
+  } else if (FsVolume::m_cwv && FsVolume::m_cwv->m_xVol) {
+    m_xFile = new (m_fileMem) ExFatFile;
+    if (m_xFile->openCwd()) {
+      return true;
+    }
+    m_xFile = nullptr;
+  }
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FsBaseFile::openNext(FsBaseFile* dir, oflag_t oflag) {
+  close();
+  if (dir->m_fFile) {
+    m_fFile = new (m_fileMem) FatFile;
+    if (m_fFile->openNext(dir->m_fFile, oflag)) {
+      return true;
+    }
+    m_fFile = nullptr;
+  } else if (dir->m_xFile) {
+    m_xFile = new (m_fileMem) ExFatFile;
+    if (m_xFile->openNext(dir->m_xFile, oflag)) {
+      return true;
+    }
+    m_xFile = nullptr;
+  }
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FsBaseFile::openRoot(FsVolume* vol) {
+  if (!vol) {
+    return false;
+  }
+  close();
+  if (vol->m_fVol) {
+    m_fFile = new (m_fileMem) FatFile;
+    if (m_fFile && m_fFile->openRoot(vol->m_fVol)) {
+      return true;
+    }
+    m_fFile = nullptr;
+  } else if (vol->m_xVol) {
+    m_xFile = new (m_fileMem) ExFatFile;
+    if (m_xFile && m_xFile->openRoot(vol->m_xVol)) {
+      return true;
+    }
+    m_xFile = nullptr;
+  }
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FsBaseFile::remove() {
+  if (m_fFile) {
+    if (m_fFile->remove()) {
+      m_fFile = nullptr;
+      return true;
+    }
+  } else if (m_xFile) {
+    if (m_xFile->remove()) {
+      m_xFile = nullptr;
+      return true;
+    }
+  }
+  return false;
+}
+//------------------------------------------------------------------------------
+bool FsBaseFile::rmdir() {
+  if (m_fFile) {
+    if (m_fFile->rmdir()) {
+      m_fFile = nullptr;
+      return true;
+    }
+  } else if (m_xFile) {
+    if (m_xFile->rmdir()) {
+      m_xFile = nullptr;
+      return true;
+    }
+  }
+  return false;
+}

+ 858 - 0
lib/SdFat_NoArduino/src/FsLib/FsFile.h

@@ -0,0 +1,858 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FsFile_h
+#define FsFile_h
+/**
+ * \file
+ * \brief FsBaseFile include file.
+ */
+#include "FsNew.h"
+#include "FatLib/FatLib.h"
+#include "ExFatLib/ExFatLib.h"
+/**
+ * \class FsBaseFile
+ * \brief FsBaseFile class.
+ */
+class FsBaseFile {
+ public:
+  /** Create an instance. */
+  FsBaseFile() {}
+  /**  Create a file object and open it in the current working directory.
+   *
+   * \param[in] path A path for a file to be opened.
+   *
+   * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
+   * OR of open flags. see FatFile::open(FatFile*, const char*, uint8_t).
+   */
+  FsBaseFile(const char* path, oflag_t oflag) {
+    open(path, oflag);
+  }
+
+  ~FsBaseFile() {close();}
+  /** Copy constructor.
+   *
+   * \param[in] from Object used to initialize this instance.
+   */
+  FsBaseFile(const FsBaseFile& from);
+  /** Copy assignment operator
+   * \param[in] from Object used to initialize this instance.
+   * \return assigned object.
+   */
+  FsBaseFile& operator=(const FsBaseFile& from);
+  /** The parenthesis operator.
+    *
+    * \return true if a file is open.
+    */
+  operator bool() const {return isOpen();}
+  /**
+   * \return user settable file attributes for success else -1.
+   */
+  int attrib() {
+     return m_fFile ? m_fFile->attrib() :
+            m_xFile ? m_xFile->attrib() : -1;
+  }
+  /** Set file attributes
+   *
+   * \param[in] bits bit-wise or of selected attributes: FS_ATTRIB_READ_ONLY,
+   *            FS_ATTRIB_HIDDEN, FS_ATTRIB_SYSTEM, FS_ATTRIB_ARCHIVE.
+   *
+   * \note attrib() will fail for set read-only if the file is open for write.
+   * \return true for success or false for failure.
+   */
+  bool attrib(uint8_t bits) {
+    return m_fFile ? m_fFile->attrib(bits) :
+           m_xFile ? m_xFile->attrib(bits) : false;
+  }
+  /** \return number of bytes available from the current position to EOF
+   *   or INT_MAX if more than INT_MAX bytes are available.
+   */
+  int available() const {
+    return m_fFile ? m_fFile->available() :
+           m_xFile ? m_xFile->available() : 0;
+  }
+  /** \return The number of bytes available from the current position
+   * to EOF for normal files.  Zero is returned for directory files.
+   */
+  uint64_t available64() const {
+    return m_fFile ? m_fFile->available32() :
+           m_xFile ? m_xFile->available64() : 0;
+  }
+  /** Clear writeError. */
+  void clearWriteError() {
+    if (m_fFile) m_fFile->clearWriteError();
+    if (m_xFile) m_xFile->clearWriteError();
+  }
+  /** Close a file and force cached data and directory information
+   *  to be written to the storage device.
+   *
+   * \return true for success or false for failure.
+   */
+  bool close();
+  /** Check for contiguous file and return its raw sector range.
+   *
+   * \param[out] bgnSector the first sector address for the file.
+   * \param[out] endSector the last  sector address for the file.
+   *
+   * Set contiguous flag for FAT16/FAT32 files.
+   * Parameters may be nullptr.
+   *
+   * \return true for success or false for failure.
+   */
+  bool contiguousRange(uint32_t* bgnSector, uint32_t* endSector) {
+    return m_fFile ? m_fFile->contiguousRange(bgnSector, endSector) :
+           m_xFile ? m_xFile->contiguousRange(bgnSector, endSector) : false;
+  }
+  /** \return The current cluster number for a file or directory. */
+  uint32_t curCluster() const {
+    return m_fFile ? m_fFile->curCluster() :
+           m_xFile ? m_xFile->curCluster() : 0;
+  }
+  /** \return The current position for a file or directory. */
+  uint64_t curPosition() const {
+    return m_fFile ? m_fFile->curPosition() :
+           m_xFile ? m_xFile->curPosition() : 0;
+  }
+  /** \return Directory entry index. */
+  uint32_t dirIndex() const {
+    return m_fFile ? m_fFile->dirIndex() :
+           m_xFile ? m_xFile->dirIndex() : 0;
+  }
+  /** Test for the existence of a file in a directory
+   *
+   * \param[in] path Path of the file to be tested for.
+   *
+   * The calling instance must be an open directory file.
+   *
+   * dirFile.exists("TOFIND.TXT") searches for "TOFIND.TXT" in  the directory
+   * dirFile.
+   *
+   * \return true if the file exists else false.
+   */
+  bool exists(const char* path) {
+    return m_fFile ? m_fFile->exists(path) :
+           m_xFile ? m_xFile->exists(path) : false;
+  }
+  /** get position for streams
+   * \param[out] pos struct to receive position
+   */
+  void fgetpos(fspos_t* pos) const {
+    if (m_fFile) m_fFile->fgetpos(pos);
+    if (m_xFile) m_xFile->fgetpos(pos);
+  }
+ /**
+   * Get a string from a file.
+   *
+   * fgets() reads bytes from a file into the array pointed to by \a str, until
+   * \a num - 1 bytes are read, or a delimiter is read and transferred to \a str,
+   * or end-of-file is encountered. The string is then terminated
+   * with a null byte.
+   *
+   * fgets() deletes CR, '\\r', from the string.  This insures only a '\\n'
+   * terminates the string for Windows text files which use CRLF for newline.
+   *
+   * \param[out] str Pointer to the array where the string is stored.
+   * \param[in] num Maximum number of characters to be read
+   * (including the final null byte). Usually the length
+   * of the array \a str is used.
+   * \param[in] delim Optional set of delimiters. The default is "\n".
+   *
+   * \return For success fgets() returns the length of the string in \a str.
+   * If no data is read, fgets() returns zero for EOF or -1 if an error occurred.
+   */
+  int fgets(char* str, int num, char* delim = nullptr) {
+    return m_fFile ? m_fFile->fgets(str, num, delim) :
+           m_xFile ? m_xFile->fgets(str, num, delim) : -1;
+  }
+  /** \return The total number of bytes in a file. */
+  uint64_t fileSize() const {
+    return m_fFile ? m_fFile->fileSize() :
+           m_xFile ? m_xFile->fileSize() : 0;
+  }
+  /** \return Address of first sector or zero for empty file. */
+  uint32_t firstSector() const {
+    return m_fFile ? m_fFile->firstSector() :
+           m_xFile ? m_xFile->firstSector() : 0;
+  }
+  /** Ensure that any bytes written to the file are saved to the SD card. */
+  void flush() {sync();}
+  /** set position for streams
+   * \param[in] pos struct with value for new position
+   */
+  void fsetpos(const fspos_t* pos) {
+    if (m_fFile) m_fFile->fsetpos(pos);
+    if (m_xFile) m_xFile->fsetpos(pos);
+  }
+  /** Get a file's access date and time.
+   *
+   * \param[out] pdate Packed date for directory entry.
+   * \param[out] ptime Packed time for directory entry.
+   *
+   * \return true for success or false for failure.
+   */
+  bool getAccessDateTime(uint16_t* pdate, uint16_t* ptime) {
+    return m_fFile ? m_fFile->getAccessDateTime(pdate, ptime) :
+           m_xFile ? m_xFile->getAccessDateTime(pdate, ptime) : false;
+  }
+  /** Get a file's create date and time.
+   *
+   * \param[out] pdate Packed date for directory entry.
+   * \param[out] ptime Packed time for directory entry.
+   *
+   * \return true for success or false for failure.
+   */
+  bool getCreateDateTime(uint16_t* pdate, uint16_t* ptime) {
+    return m_fFile ? m_fFile->getCreateDateTime(pdate, ptime) :
+           m_xFile ? m_xFile->getCreateDateTime(pdate, ptime) : false;
+  }
+  /** \return All error bits. */
+  uint8_t getError() const {
+    return m_fFile ? m_fFile->getError() :
+           m_xFile ? m_xFile->getError() : 0XFF;
+  }
+  /** Get a file's Modify date and time.
+   *
+   * \param[out] pdate Packed date for directory entry.
+   * \param[out] ptime Packed time for directory entry.
+   *
+   * \return true for success or false for failure.
+   */
+  bool getModifyDateTime(uint16_t* pdate, uint16_t* ptime) {
+    return m_fFile ? m_fFile->getModifyDateTime(pdate, ptime) :
+           m_xFile ? m_xFile->getModifyDateTime(pdate, ptime) : false;
+  }
+  /**
+   * Get a file's name followed by a zero byte.
+   *
+   * \param[out] name An array of characters for the file's name.
+   * \param[in] len The size of the array in bytes. The array
+   *             must be at least 13 bytes long.  The file's name will be
+   *             truncated if the file's name is too long.
+   * \return The length of the returned string.
+   */
+  size_t getName(char* name, size_t len) {
+    *name = 0;
+    return m_fFile ? m_fFile->getName(name, len) :
+           m_xFile ? m_xFile->getName(name, len) : 0;
+  }
+
+  /** \return value of writeError */
+  bool getWriteError() const {
+    return m_fFile ? m_fFile->getWriteError() :
+           m_xFile ? m_xFile->getWriteError() : true;
+  }
+  /**
+   * Check for FsBlockDevice busy.
+   *
+   * \return true if busy else false.
+   */
+  bool isBusy() {
+    return m_fFile ? m_fFile->isBusy() :
+           m_xFile ? m_xFile->isBusy() : true;
+  }
+  /** \return True if the file is contiguous. */
+  bool isContiguous() const {
+#if USE_FAT_FILE_FLAG_CONTIGUOUS
+    return m_fFile ? m_fFile->isContiguous() :
+           m_xFile ? m_xFile->isContiguous() : false;
+#else  // USE_FAT_FILE_FLAG_CONTIGUOUS
+    return m_xFile ? m_xFile->isContiguous() : false;
+#endif  // USE_FAT_FILE_FLAG_CONTIGUOUS
+  }
+  /** \return True if this is a directory else false. */
+  bool isDir() const {
+    return m_fFile ? m_fFile->isDir() :
+           m_xFile ? m_xFile->isDir() : false;
+  }
+  /** This function reports if the current file is a directory or not.
+   * \return true if the file is a directory.
+   */
+  bool isDirectory() const {return isDir();}
+  /** \return True if this is a normal file. */
+  bool isFile() const {
+    return m_fFile ? m_fFile->isFile() :
+           m_xFile ? m_xFile->isFile() : false;
+  }
+  /** \return True if this is a normal file or sub-directory. */
+  bool isFileOrSubDir() const {
+    return m_fFile ? m_fFile->isFileOrSubDir() :
+           m_xFile ? m_xFile->isFileOrSubDir() : false;
+  }
+  /** \return True if this is a hidden file else false. */
+  bool isHidden() const {
+    return m_fFile ? m_fFile->isHidden() :
+           m_xFile ? m_xFile->isHidden() : false;
+  }
+  /** \return True if this is an open file/directory else false. */
+  bool isOpen() const {return m_fFile || m_xFile;}
+  /** \return True file is readable. */
+  bool isReadable() const {
+    return m_fFile ? m_fFile->isReadable() :
+           m_xFile ? m_xFile->isReadable() : false;
+    }
+  /** \return True if file is read-only */
+  bool isReadOnly() const {
+    return m_fFile ? m_fFile->isReadOnly() :
+           m_xFile ? m_xFile->isReadOnly() : false;
+  }
+  /** \return True if this is a sub-directory file else false. */
+  bool isSubDir() const {
+    return m_fFile ? m_fFile->isSubDir() :
+           m_xFile ? m_xFile->isSubDir() : false;
+  }
+  /** \return True file is writable. */
+  bool isWritable() const {
+    return m_fFile ? m_fFile->isWritable() :
+           m_xFile ? m_xFile->isWritable() : false;
+  }
+#if ENABLE_ARDUINO_SERIAL
+  /** List directory contents.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   */
+  bool ls(uint8_t flags) {
+    return ls(&Serial, flags);
+  }
+  /** List directory contents. */
+  bool ls() {
+    return ls(&Serial);
+  }
+#endif  // ENABLE_ARDUINO_SERIAL
+  /** List directory contents.
+   *
+   * \param[in] pr Print object.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(print_t* pr) {
+    return m_fFile ? m_fFile->ls(pr) :
+           m_xFile ? m_xFile->ls(pr) : false;
+  }
+  /** List directory contents.
+   *
+   * \param[in] pr Print object.
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(print_t* pr, uint8_t flags) {
+    return m_fFile ? m_fFile->ls(pr, flags) :
+           m_xFile ? m_xFile->ls(pr, flags) : false;
+  }
+  /** Make a new directory.
+   *
+   * \param[in] dir An open FatFile instance for the directory that will
+   *                   contain the new directory.
+   *
+   * \param[in] path A path with a valid 8.3 DOS name for the new directory.
+   *
+   * \param[in] pFlag Create missing parent directories if true.
+   *
+   * \return true for success or false for failure.
+   */
+  bool mkdir(FsBaseFile* dir, const char* path, bool pFlag = true);
+  /** Open a file or directory by name.
+   *
+   * \param[in] dir An open file instance for the directory containing
+   *                    the file to be opened.
+   *
+   * \param[in] path A path with a valid 8.3 DOS name for a file to be opened.
+   *
+   * \param[in] oflag Values for \a oflag are constructed by a
+   *                  bitwise-inclusive OR of flags from the following list
+   *
+   * O_RDONLY - Open for reading only..
+   *
+   * O_READ - Same as O_RDONLY.
+   *
+   * O_WRONLY - Open for writing only.
+   *
+   * O_WRITE - Same as O_WRONLY.
+   *
+   * O_RDWR - Open for reading and writing.
+   *
+   * O_APPEND - If set, the file offset shall be set to the end of the
+   * file prior to each write.
+   *
+   * O_AT_END - Set the initial position at the end of the file.
+   *
+   * O_CREAT - If the file exists, this flag has no effect except as noted
+   * under O_EXCL below. Otherwise, the file shall be created
+   *
+   * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file exists.
+   *
+   * O_TRUNC - If the file exists and is a regular file, and the file is
+   * successfully opened and is not read only, its length shall be truncated to 0.
+   *
+   * WARNING: A given file must not be opened by more than one file object
+   * or file corruption may occur.
+   *
+   * \note Directory files must be opened read only.  Write and truncation is
+   * not allowed for directory files.
+   *
+   * \return true for success or false for failure.
+   */
+  bool open(FsBaseFile* dir, const char* path, oflag_t oflag = O_RDONLY);
+  /** Open a file by index.
+   *
+   * \param[in] dir An open FsFile instance for the directory.
+   *
+   * \param[in] index The \a index of the directory entry for the file to be
+   * opened.  The value for \a index is (directory file position)/32.
+   *
+   * \param[in] oflag bitwise-inclusive OR of open flags.
+   *            See see FsFile::open(FsFile*, const char*, uint8_t).
+   *
+   * See open() by path for definition of flags.
+   * \return true for success or false for failure.
+   */
+  bool open(FsBaseFile* dir, uint32_t index, oflag_t oflag = O_RDONLY);
+  /** Open a file or directory by name.
+   *
+   * \param[in] vol Volume where the file is located.
+   *
+   * \param[in] path A path for a file to be opened.
+   *
+   * \param[in] oflag Values for \a oflag are constructed by a
+   *                  bitwise-inclusive OR of open flags.
+   *
+   * \return true for success or false for failure.
+   */
+  bool open(FsVolume* vol, const char* path, oflag_t oflag = O_RDONLY);
+  /** Open a file or directory by name.
+   *
+   * \param[in] path A path for a file to be opened.
+   *
+   * \param[in] oflag Values for \a oflag are constructed by a
+   *                  bitwise-inclusive OR of open flags.
+   *
+   * \return true for success or false for failure.
+   */
+  bool open(const char* path, oflag_t oflag = O_RDONLY) {
+    return FsVolume::m_cwv && open(FsVolume::m_cwv, path, oflag);
+  }
+   /** Open a file or directory by index in the current working directory.
+   *
+   * \param[in] index The \a index of the directory entry for the file to be
+   * opened.  The value for \a index is (directory file position)/32.
+   *
+   * \param[in] oflag Values for \a oflag are constructed by a
+   *                  bitwise-inclusive OR of open flags.
+   *
+   * \return true for success or false for failure.
+   */
+  bool open(uint32_t index, oflag_t oflag = O_RDONLY) {
+    FsBaseFile cwd;
+    return cwd.openCwd() && open(&cwd, index, oflag);
+  }
+  /** Open the current working directory.
+   *
+   * \return true for success or false for failure.
+   */
+  bool openCwd();
+  /** Opens the next file or folder in a directory.
+   * \param[in] dir directory containing files.
+   * \param[in] oflag open flags.
+   * \return a file object.
+   */
+  bool openNext(FsBaseFile* dir, oflag_t oflag = O_RDONLY);
+  /** Open a volume's root directory.
+   *
+   * \param[in] vol The SdFs volume containing the root directory to be opened.
+   *
+   * \return true for success or false for failure.
+   */
+  bool openRoot(FsVolume* vol);
+  /** \return the current file position. */
+  uint64_t position() const {return curPosition();}
+  /** Return the next available byte without consuming it.
+   *
+   * \return The byte if no error and not at eof else -1;
+   */
+  int peek() {
+    return m_fFile ? m_fFile->peek() :
+           m_xFile ? m_xFile->peek() : -1;
+  }
+  /** Allocate contiguous clusters to an empty file.
+   *
+   * The file must be empty with no clusters allocated.
+   *
+   * The file will contain uninitialized data for FAT16/FAT32 files.
+   * exFAT files will have zero validLength and dataLength will equal
+   * the requested length.
+   *
+   * \param[in] length size of the file in bytes.
+   * \return true for success or false for failure.
+   */
+  bool preAllocate(uint64_t length) {
+    return m_fFile ? length < (1ULL << 32) && m_fFile->preAllocate(length) :
+           m_xFile ? m_xFile->preAllocate(length) : false;
+  }
+  /** Print a file's access date and time
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return true for success or false for failure.
+   */
+  size_t printAccessDateTime(print_t* pr) {
+    return m_fFile ? m_fFile->printAccessDateTime(pr) :
+           m_xFile ? m_xFile->printAccessDateTime(pr) : 0;
+  }
+  /** Print a file's creation date and time
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return true for success or false for failure.
+   */
+  size_t printCreateDateTime(print_t* pr) {
+    return m_fFile ? m_fFile->printCreateDateTime(pr) :
+           m_xFile ? m_xFile->printCreateDateTime(pr) : 0;
+  }
+  /** Print a number followed by a field terminator.
+   * \param[in] value The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \param[in] prec Number of digits after decimal point.
+   * \return The number of bytes written or -1 if an error occurs.
+   */
+  size_t printField(double value, char term, uint8_t prec = 2) {
+    return m_fFile ? m_fFile->printField(value, term, prec) :
+           m_xFile ? m_xFile->printField(value, term, prec) : 0;
+  }
+  /** Print a number followed by a field terminator.
+   * \param[in] value The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \param[in] prec Number of digits after decimal point.
+   * \return The number of bytes written or -1 if an error occurs.
+   */
+  size_t printField(float value, char term, uint8_t prec = 2) {
+     return printField(static_cast<double>(value), term, prec);
+  }
+  /** Print a number followed by a field terminator.
+   * \param[in] value The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \return The number of bytes written or -1 if an error occurs.
+   */
+  template<typename Type>
+  size_t printField(Type value, char term) {
+    return m_fFile ? m_fFile->printField(value, term) :
+           m_xFile ? m_xFile->printField(value, term) : 0;
+  }
+  /** Print a file's size.
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return The number of characters printed is returned
+   *         for success and zero is returned for failure.
+   */
+  size_t printFileSize(print_t* pr) {
+    return m_fFile ? m_fFile->printFileSize(pr) :
+           m_xFile ? m_xFile->printFileSize(pr) : 0;
+  }
+  /** Print a file's modify date and time
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return true for success or false for failure.
+   */
+  size_t printModifyDateTime(print_t* pr) {
+    return m_fFile ? m_fFile->printModifyDateTime(pr) :
+           m_xFile ? m_xFile->printModifyDateTime(pr) : 0;
+  }
+  /** Print a file's name
+   *
+   * \param[in] pr Print stream for output.
+   *
+   * \return true for success or false for failure.
+   */
+  size_t printName(print_t* pr) {
+    return m_fFile ? m_fFile->printName(pr) :
+           m_xFile ? m_xFile->printName(pr) : 0;
+  }
+  /** Read the next byte from a file.
+   *
+   * \return For success return the next byte in the file as an int.
+   * If an error occurs or end of file is reached return -1.
+   */
+  int read() {
+    uint8_t b;
+    return read(&b, 1) == 1 ? b : -1;
+  }
+  /** Read data from a file starting at the current position.
+   *
+   * \param[out] buf Pointer to the location that will receive the data.
+   *
+   * \param[in] count Maximum number of bytes to read.
+   *
+   * \return For success read() returns the number of bytes read.
+   * A value less than \a count, including zero, will be returned
+   * if end of file is reached.
+   * If an error occurs, read() returns -1.  Possible errors include
+   * read() called before a file has been opened, corrupt file system
+   * or an I/O error occurred.
+   */
+  int read(void* buf, size_t count) {
+    return m_fFile ? m_fFile->read(buf, count) :
+           m_xFile ? m_xFile->read(buf, count) : -1;
+  }
+  /** Remove a file.
+   *
+   * The directory entry and all data for the file are deleted.
+   *
+   * \note This function should not be used to delete the 8.3 version of a
+   * file that has a long name. For example if a file has the long name
+   * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
+   *
+   * \return true for success or false for failure.
+   */
+  bool remove();
+   /** Remove a file.
+   *
+   * The directory entry and all data for the file are deleted.
+   *
+   * \param[in] path Path for the file to be removed.
+   *
+   * Example use: dirFile.remove(filenameToRemove);
+   *
+   * \note This function should not be used to delete the 8.3 version of a
+   * file that has a long name. For example if a file has the long name
+   * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
+   *
+   * \return true for success or false for failure.
+   */
+  bool remove(const char* path) {
+    return m_fFile ? m_fFile->remove(path) :
+           m_xFile ? m_xFile->remove(path) : false;
+  }
+  /** Rename a file or subdirectory.
+   *
+   * \param[in] newPath New path name for the file/directory.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rename(const char* newPath) {
+    return m_fFile ? m_fFile->rename(newPath) :
+           m_xFile ? m_xFile->rename(newPath) : false;
+  }
+  /** Rename a file or subdirectory.
+   *
+   * \param[in] dir Directory for the new path.
+   * \param[in] newPath New path name for the file/directory.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rename(FsBaseFile* dir, const char* newPath) {
+    return m_fFile && dir->m_fFile ? m_fFile->rename(dir->m_fFile, newPath) :
+           m_xFile && dir->m_xFile ? m_xFile->rename(dir->m_xFile, newPath) :
+           false;
+  }
+  /** Set the file's current position to zero. */
+  void rewind() {
+    if (m_fFile) m_fFile->rewind();
+    if (m_xFile) m_xFile->rewind();
+  }
+  /** Rewind a file if it is a directory */
+  void rewindDirectory() {
+    if (isDir()) rewind();
+  }
+  /** Remove a directory file.
+   *
+   * The directory file will be removed only if it is empty and is not the
+   * root directory.  rmdir() follows DOS and Windows and ignores the
+   * read-only attribute for the directory.
+   *
+   * \note This function should not be used to delete the 8.3 version of a
+   * directory that has a long name. For example if a directory has the
+   * long name "New folder" you should not delete the 8.3 name "NEWFOL~1".
+   *
+   * \return true for success or false for failure.
+   */
+  bool rmdir();
+  /** Seek to a new position in the file, which must be between
+   * 0 and the size of the file (inclusive).
+   *
+   * \param[in] pos the new file position.
+   * \return true for success or false for failure.
+   */
+  bool seek(uint64_t pos) {return seekSet(pos);}
+  /** Set the files position to current position + \a pos. See seekSet().
+   * \param[in] offset The new position in bytes from the current position.
+   * \return true for success or false for failure.
+   */
+  bool seekCur(int64_t offset) {
+    return seekSet(curPosition() + offset);
+  }
+  /** Set the files position to end-of-file + \a offset. See seekSet().
+   * Can't be used for directory files since file size is not defined.
+   * \param[in] offset The new position in bytes from end-of-file.
+   * \return true for success or false for failure.
+   */
+  bool seekEnd(int64_t offset = 0) {
+    return seekSet(fileSize() + offset);
+  }
+  /** Sets a file's position.
+   *
+   * \param[in] pos The new position in bytes from the beginning of the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool seekSet(uint64_t pos) {
+    return m_fFile ? pos < (1ULL << 32) && m_fFile->seekSet(pos) :
+           m_xFile ? m_xFile->seekSet(pos) : false;
+  }
+  /** \return the file's size. */
+  uint64_t size() const {return fileSize();}
+  /** The sync() call causes all modified data and directory fields
+   * to be written to the storage device.
+   *
+   * \return true for success or false for failure.
+   */
+  bool sync() {
+    return m_fFile ? m_fFile->sync() :
+           m_xFile ? m_xFile->sync() : false;
+  }
+  /** Set a file's timestamps in its directory entry.
+   *
+   * \param[in] flags Values for \a flags are constructed by a bitwise-inclusive
+   * OR of flags from the following list
+   *
+   * T_ACCESS - Set the file's last access date and time.
+   *
+   * T_CREATE - Set the file's creation date and time.
+   *
+   * T_WRITE - Set the file's last write/modification date and time.
+   *
+   * \param[in] year Valid range 1980 - 2107 inclusive.
+   *
+   * \param[in] month Valid range 1 - 12 inclusive.
+   *
+   * \param[in] day Valid range 1 - 31 inclusive.
+   *
+   * \param[in] hour Valid range 0 - 23 inclusive.
+   *
+   * \param[in] minute Valid range 0 - 59 inclusive.
+   *
+   * \param[in] second Valid range 0 - 59 inclusive
+   *
+   * \note It is possible to set an invalid date since there is no check for
+   * the number of days in a month.
+   *
+   * \note
+   * Modify and access timestamps may be overwritten if a date time callback
+   * function has been set by dateTimeCallback().
+   *
+   * \return true for success or false for failure.
+   */
+  bool timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day,
+                 uint8_t hour, uint8_t minute, uint8_t second) {
+    return m_fFile ?
+           m_fFile->timestamp(flags, year, month, day, hour, minute, second) :
+           m_xFile ?
+           m_xFile->timestamp(flags, year, month, day, hour, minute, second) :
+           false;
+  }
+  /** Truncate a file to the current position.
+   *
+   * \return true for success or false for failure.
+   */
+  bool truncate() {
+    return m_fFile ? m_fFile->truncate() :
+           m_xFile ? m_xFile->truncate() : false;
+  }
+  /** Truncate a file to a specified length.
+   * The current file position will be set to end of file.
+   *
+   * \param[in] length The desired length for the file.
+   *
+   * \return true for success or false for failure.
+   */
+  bool truncate(uint64_t length) {
+    return m_fFile ? length < (1ULL << 32) && m_fFile->truncate(length) :
+           m_xFile ? m_xFile->truncate(length) : false;
+  }
+  /** Write a string to a file. Used by the Arduino Print class.
+   * \param[in] str Pointer to the string.
+   * Use getWriteError to check for errors.
+   * \return count of characters written for success or -1 for failure.
+   */
+  size_t write(const char* str) {
+    return write(str, strlen(str));
+  }
+  /** Write a byte to a file. Required by the Arduino Print class.
+   * \param[in] b the byte to be written.
+   * Use getWriteError to check for errors.
+   * \return 1 for success and 0 for failure.
+   */
+  size_t write(uint8_t b) {return write(&b, 1);}
+  /** Write data to an open file.
+   *
+   * \note Data is moved to the cache but may not be written to the
+   * storage device until sync() is called.
+   *
+   * \param[in] buf Pointer to the location of the data to be written.
+   *
+   * \param[in] count Number of bytes to write.
+   *
+   * \return For success write() returns the number of bytes written, always
+   * \a nbyte.  If an error occurs, write() returns zero and writeError is set.
+   */
+  size_t write(const void* buf, size_t count) {
+    return m_fFile ? m_fFile->write(buf, count) :
+           m_xFile ? m_xFile->write(buf, count) : 0;
+  }
+
+ private:
+  newalign_t m_fileMem[FS_ALIGN_DIM(ExFatFile, FatFile)];
+  FatFile*   m_fFile = nullptr;
+  ExFatFile* m_xFile = nullptr;
+};
+/**
+ * \class FsFile
+ * \brief FsBaseFile file with Arduino Stream.
+ */
+class FsFile : public StreamFile<FsBaseFile, uint64_t> {
+ public:
+  /** Opens the next file or folder in a directory.
+   *
+   * \param[in] oflag open flags.
+   * \return a FatStream object.
+   */
+  FsFile openNextFile(oflag_t oflag = O_RDONLY) {
+    FsFile tmpFile;
+    tmpFile.openNext(this, oflag);
+    return tmpFile;
+  }
+};
+#endif  // FsFile_h

+ 57 - 0
lib/SdFat_NoArduino/src/FsLib/FsFormatter.h

@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FsFormatter_h
+#define FsFormatter_h
+#include "FatLib/FatLib.h"
+#include "ExFatLib/ExFatLib.h"
+/**
+ * \class FsFormatter
+ * \brief Format a exFAT/FAT volume.
+ */
+class FsFormatter {
+ public:
+  /**
+   * Format a FAT volume.
+   *
+   * \param[in] dev Block device for volume.
+   * \param[in] secBuffer buffer for writing to volume.
+   * \param[in] pr Print device for progress output.
+   *
+   * \return true for success or false for failure.
+   */
+  bool format(FsBlockDevice* dev, uint8_t* secBuffer, print_t* pr = nullptr) {
+    uint32_t sectorCount = dev->sectorCount();
+    if (sectorCount == 0) {
+      return false;
+    }
+    return sectorCount <= 67108864 ?
+      m_fFmt.format(dev, secBuffer, pr) :
+      m_xFmt.format(dev, secBuffer, pr);
+  }
+ private:
+  FatFormatter m_fFmt;
+  ExFatFormatter m_xFmt;
+};
+#endif  // FsFormatter_h

+ 34 - 0
lib/SdFat_NoArduino/src/FsLib/FsLib.h

@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FsLib_h
+#define FsLib_h
+/**
+ * \file
+ * \brief FsLib include file.
+ */
+#include "FsVolume.h"
+#include "FsFile.h"
+#include "FsFormatter.h"
+#endif  // FsLib_h

+ 29 - 0
lib/SdFat_NoArduino/src/FsLib/FsNew.cpp

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "FsNew.h"
+void* operator new(size_t size, newalign_t* ptr) {
+  (void)size;
+  return ptr;
+}

+ 46 - 0
lib/SdFat_NoArduino/src/FsLib/FsNew.h

@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FsNew_h
+#define FsNew_h
+#include <stddef.h>
+#include <stdint.h>
+
+/** 32-bit alignment */
+typedef uint32_t newalign_t;
+
+/** Size required for exFAT or FAT class. */
+#define FS_SIZE(etype, ftype) \
+  (sizeof(ftype) < sizeof(etype) ? sizeof(etype) : sizeof(ftype))
+
+/** Dimension of aligned area. */
+#define NEW_ALIGN_DIM(n) \
+  (((size_t)(n) + sizeof(newalign_t) - 1U)/sizeof(newalign_t))
+
+/** Dimension of aligned area for etype or ftype class. */
+#define FS_ALIGN_DIM(etype, ftype) NEW_ALIGN_DIM(FS_SIZE(etype, ftype))
+
+/** Custom new placement operator */
+void* operator new(size_t size, newalign_t* ptr);
+#endif  // FsNew_h

+ 66 - 0
lib/SdFat_NoArduino/src/FsLib/FsVolume.cpp

@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "FsLib.h"
+FsVolume* FsVolume::m_cwv = nullptr;
+//------------------------------------------------------------------------------
+bool FsVolume::begin(FsBlockDevice* blockDev, bool setCwv,
+                     uint8_t part, uint32_t volStart) {
+  m_blockDev = blockDev;
+  m_fVol = nullptr;
+  m_xVol = new (m_volMem) ExFatVolume;
+  if (m_xVol && m_xVol->begin(m_blockDev, false, part, volStart)) {
+    goto done;
+  }
+  m_xVol = nullptr;
+  m_fVol = new (m_volMem) FatVolume;
+  if (m_fVol && m_fVol->begin(m_blockDev, false, part, volStart)) {
+    goto done;
+  }
+  m_fVol = nullptr;
+  return false;
+
+ done:
+  if (setCwv || !m_cwv) {
+    m_cwv = this;
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+bool FsVolume::ls(print_t* pr, const char* path, uint8_t flags) {
+  FsBaseFile dir;
+  return dir.open(this, path, O_RDONLY) && dir.ls(pr, flags);
+}
+//------------------------------------------------------------------------------
+FsFile FsVolume::open(const char *path, oflag_t oflag) {
+  FsFile tmpFile;
+  tmpFile.open(this, path, oflag);
+  return tmpFile;
+}
+#if ENABLE_ARDUINO_STRING
+//------------------------------------------------------------------------------
+FsFile FsVolume::open(const String &path, oflag_t oflag) {
+  return open(path.c_str(), oflag );
+}
+#endif  // ENABLE_ARDUINO_STRING

+ 410 - 0
lib/SdFat_NoArduino/src/FsLib/FsVolume.h

@@ -0,0 +1,410 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FsVolume_h
+#define FsVolume_h
+/**
+ * \file
+ * \brief FsVolume include file.
+ */
+#include "FsNew.h"
+#include "../FatLib/FatLib.h"
+#include "../ExFatLib/ExFatLib.h"
+
+class FsFile;
+/**
+ * \class FsVolume
+ * \brief FsVolume class.
+ */
+class FsVolume {
+ public:
+  FsVolume() {}
+
+  ~FsVolume() {end();}
+  /** Get file's user settable attributes.
+   * \param[in] path path to file.
+   * \return user settable file attributes for success else -1.
+   */
+  int attrib(const char* path) {
+    return m_fVol ? m_fVol->attrib(path) :
+           m_xVol ? m_xVol->attrib(path) : -1;
+  }
+  /** Set file's user settable attributes.
+   * \param[in] path path to file.
+   * \param[in] bits bit-wise or of selected attributes: FS_ATTRIB_READ_ONLY,
+   *            FS_ATTRIB_HIDDEN, FS_ATTRIB_SYSTEM, FS_ATTRIB_ARCHIVE.
+   *
+   * \return true for success or false for failure.
+   */
+  bool attrib(const char* path, uint8_t bits) {
+    return m_fVol ? m_fVol->attrib(path, bits) :
+           m_xVol ? m_xVol->attrib(path, bits) : false;
+  }
+  /**
+   * Initialize an FatVolume object.
+   * \param[in] blockDev Device block driver.
+   * \param[in] setCwv Set current working volume if true.
+   * \param[in] part partition to initialize.
+   * \param[in] volStart Start sector of volume if part is zero.
+   * \return true for success or false for failure.
+   */
+  bool begin(FsBlockDevice* blockDev, bool setCwv = true, uint8_t
+             part = 1, uint32_t volStart = 0);
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+  uint32_t __attribute__((error("use sectorsPerCluster()"))) blocksPerCluster();
+#endif  // DOXYGEN_SHOULD_SKIP_THIS
+  /** \return the number of bytes in a cluster. */
+  uint32_t bytesPerCluster() const {
+    return m_fVol ? m_fVol->bytesPerCluster() :
+           m_xVol ? m_xVol->bytesPerCluster() : 0;
+  }
+  /**
+   * Set volume working directory to root.
+   * \return true for success or false for failure.
+   */
+  bool chdir() {
+    return m_fVol ? m_fVol->chdir() :
+           m_xVol ? m_xVol->chdir() : false;
+  }
+  /**
+   * Set volume working directory.
+   * \param[in] path Path for volume working directory.
+   * \return true for success or false for failure.
+   */
+    bool chdir(const char* path) {
+    return m_fVol ? m_fVol->chdir(path) :
+           m_xVol ? m_xVol->chdir(path) : false;
+  }
+  /** Change global working volume to this volume. */
+  void chvol() {m_cwv = this;}
+  /** \return The total number of clusters in the volume. */
+  uint32_t clusterCount() const {
+    return m_fVol ? m_fVol->clusterCount() :
+           m_xVol ? m_xVol->clusterCount() : 0;
+  }
+  /** \return The logical sector number for the start of file data. */
+  uint32_t dataStartSector() const {
+    return m_fVol ? m_fVol->dataStartSector() :
+           m_xVol ? m_xVol->clusterHeapStartSector() : 0;
+  }
+  /** End access to volume
+   * \return pointer to sector size buffer for format.
+   */
+  uint8_t* end() {
+    m_fVol = nullptr;
+    m_xVol = nullptr;
+    static_assert(sizeof(m_volMem) >= 512, "m_volMem too small");
+    return reinterpret_cast<uint8_t*>(m_volMem);
+  }
+  /** Test for the existence of a file in a directory
+   *
+   * \param[in] path Path of the file to be tested for.
+   *
+   * \return true if the file exists else false.
+   */
+  bool exists(const char* path) {
+    return m_fVol ? m_fVol->exists(path) :
+           m_xVol ? m_xVol->exists(path) : false;
+  }
+  /** \return The logical sector number for the start of the first FAT. */
+  uint32_t fatStartSector() const {
+    return m_fVol ? m_fVol->fatStartSector() :
+           m_xVol ? m_xVol->fatStartSector() : 0;
+  }
+  /** \return Partition type, FAT_TYPE_EXFAT, FAT_TYPE_FAT32,
+   *          FAT_TYPE_FAT16, or zero for error.
+   */
+  uint8_t fatType() const {
+    return m_fVol ? m_fVol->fatType() :
+           m_xVol ? m_xVol->fatType() : 0;
+  }
+  /** \return free cluster count or -1 if an error occurs. */
+  int32_t freeClusterCount() const {
+    return m_fVol ? m_fVol->freeClusterCount() :
+           m_xVol ? m_xVol->freeClusterCount() : -1;
+  }
+  /**
+   * Check for device busy.
+   *
+   * \return true if busy else false.
+   */
+  bool isBusy() {
+    return m_fVol ? m_fVol->isBusy() :
+           m_xVol ? m_xVol->isBusy() : false;
+  }
+  /** List directory contents.
+   *
+   * \param[in] pr Print object.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(print_t* pr) {
+    return m_fVol ? m_fVol->ls(pr) :
+           m_xVol ? m_xVol->ls(pr) : false;
+  }
+  /** List directory contents.
+   *
+   * \param[in] pr Print object.
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(print_t* pr, uint8_t flags) {
+    return  m_fVol ? m_fVol->ls(pr, flags) :
+            m_xVol ? m_xVol->ls(pr, flags) : false;
+  }
+  /** List the directory contents of a directory.
+   *
+   * \param[in] pr Print stream for list.
+   *
+   * \param[in] path directory to list.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(print_t* pr, const char* path, uint8_t flags);
+   /** Make a subdirectory in the volume root directory.
+   *
+   * \param[in] path A path with a valid 8.3 DOS name for the subdirectory.
+   *
+   * \param[in] pFlag Create missing parent directories if true.
+   *
+   * \return true for success or false for failure.
+   */
+  bool mkdir(const char *path, bool pFlag = true) {
+    return m_fVol ? m_fVol->mkdir(path, pFlag) :
+           m_xVol ? m_xVol->mkdir(path, pFlag) : false;
+  }
+  /** open a file
+   *
+   * \param[in] path location of file to be opened.
+   * \param[in] oflag open flags.
+   * \return a FsBaseFile object.
+   */
+  FsFile open(const char* path, oflag_t oflag = O_RDONLY);
+  /** Remove a file from the volume root directory.
+  *
+  * \param[in] path A path with a valid 8.3 DOS name for the file.
+  *
+   * \return true for success or false for failure.
+  */
+  bool remove(const char *path) {
+    return m_fVol ? m_fVol->remove(path) :
+           m_xVol ? m_xVol->remove(path) : false;
+  }
+  /** Rename a file or subdirectory.
+   *
+   * \param[in] oldPath Path name to the file or subdirectory to be renamed.
+   *
+   * \param[in] newPath New path name of the file or subdirectory.
+   *
+   * The \a newPath object must not exist before the rename call.
+   *
+   * The file to be renamed must not be open.  The directory entry may be
+   * moved and file system corruption could occur if the file is accessed by
+   * a file object that was opened before the rename() call.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rename(const char *oldPath, const char *newPath) {
+    return m_fVol ? m_fVol->rename(oldPath, newPath) :
+           m_xVol ? m_xVol->rename(oldPath, newPath) : false;
+  }
+  /** Remove a subdirectory from the volume's root directory.
+   *
+   * \param[in] path A path with a valid 8.3 DOS name for the subdirectory.
+   *
+   * The subdirectory file will be removed only if it is empty.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rmdir(const char *path) {
+    return m_fVol ? m_fVol->rmdir(path) :
+           m_xVol ? m_xVol->rmdir(path) : false;
+  }
+  /** \return The volume's cluster size in sectors. */
+  uint32_t sectorsPerCluster() const {
+    return m_fVol ? m_fVol->sectorsPerCluster() :
+           m_xVol ? m_xVol->sectorsPerCluster() : 0;
+  }
+#if ENABLE_ARDUINO_SERIAL
+  /** List directory contents.
+   * \return true for success or false for failure.
+   */
+  bool ls() {
+    return ls(&Serial);
+  }
+  /** List directory contents.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(uint8_t flags) {
+    return ls(&Serial, flags);
+  }
+  /** List the directory contents of a directory to Serial.
+   *
+   * \param[in] path directory to list.
+   *
+   * \param[in] flags The inclusive OR of
+   *
+   * LS_DATE - %Print file modification date
+   *
+   * LS_SIZE - %Print file size.
+   *
+   * LS_R - Recursive list of subdirectories.
+   *
+   * \return true for success or false for failure.
+   *
+   * \return true for success or false for failure.
+   */
+  bool ls(const char* path, uint8_t flags = 0) {
+    return ls(&Serial, path, flags);
+  }
+#endif  // ENABLE_ARDUINO_SERIAL
+#if ENABLE_ARDUINO_STRING
+  /**
+   * Set volume working directory.
+   * \param[in] path Path for volume working directory.
+   * \return true for success or false for failure.
+   */
+  bool chdir(const String& path) {
+    return chdir(path.c_str());
+  }
+  /** Test for the existence of a file in a directory
+   *
+   * \param[in] path Path of the file to be tested for.
+   *
+   * \return true if the file exists else false.
+   */
+  bool exists(const String &path) {
+    return exists(path.c_str());
+  }
+  /** Make a subdirectory in the volume root directory.
+   *
+   * \param[in] path A path with a valid 8.3 DOS name for the subdirectory.
+   *
+   * \param[in] pFlag Create missing parent directories if true.
+   *
+   * \return true for success or false for failure.
+   */
+  bool mkdir(const String &path, bool pFlag = true) {
+    return mkdir(path.c_str(), pFlag);
+  }
+  /** open a file
+   *
+   * \param[in] path location of file to be opened.
+   * \param[in] oflag open flags.
+   * \return a FsBaseFile object.
+   */
+  FsFile open(const String &path, oflag_t oflag = O_RDONLY);
+  /** Remove a file from the volume root directory.
+  *
+  * \param[in] path A path with a valid 8.3 DOS name for the file.
+  *
+   * \return true for success or false for failure.
+  */
+  bool remove(const String &path) {
+    return remove(path.c_str());
+  }
+  /** Rename a file or subdirectory.
+   *
+   * \param[in] oldPath Path name to the file or subdirectory to be renamed.
+   *
+   * \param[in] newPath New path name of the file or subdirectory.
+   *
+   * The \a newPath object must not exist before the rename call.
+   *
+   * The file to be renamed must not be open.  The directory entry may be
+   * moved and file system corruption could occur if the file is accessed by
+   * a file object that was opened before the rename() call.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rename(const String& oldPath, const String& newPath) {
+    return rename(oldPath.c_str(), newPath.c_str());
+  }
+  /** Remove a subdirectory from the volume's root directory.
+   *
+   * \param[in] path A path with a valid 8.3 DOS name for the subdirectory.
+   *
+   * The subdirectory file will be removed only if it is empty.
+   *
+   * \return true for success or false for failure.
+   */
+  bool rmdir(const String &path) {
+    return rmdir(path.c_str());
+  }
+  /** Rename a file or subdirectory.
+   *
+   * \param[in] oldPath Path name to the file or subdirectory to be renamed.
+   *
+   * \param[in] newPath New path name of the file or subdirectory.
+   *
+   * The \a newPath object must not exist before the rename call.
+   *
+   * The file to be renamed must not be open.  The directory entry may be
+   * moved and file system corruption could occur if the file is accessed by
+   * a file object that was opened before the rename() call.
+   *
+   * \return true for success or false for failure.
+   */
+#endif  // ENABLE_ARDUINO_STRING
+
+ protected:
+  newalign_t   m_volMem[FS_ALIGN_DIM(ExFatVolume, FatVolume)];
+
+ private:
+  /** FsBaseFile allowed access to private members. */
+  friend class FsBaseFile;
+  static FsVolume* cwv() {return m_cwv;}
+  FsVolume(const FsVolume& from);
+  FsVolume& operator=(const FsVolume& from);
+
+  static FsVolume* m_cwv;
+  FatVolume*   m_fVol = nullptr;
+  ExFatVolume* m_xVol = nullptr;
+  FsBlockDevice* m_blockDev;
+};
+#endif  // FsVolume_h

+ 71 - 0
lib/SdFat_NoArduino/src/MinimumSerial.cpp

@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "common/SysCall.h"
+#if defined(UDR0) || defined(DOXYGEN)
+#include "MinimumSerial.h"
+const uint16_t MIN_2X_BAUD = F_CPU/(4*(2*0XFFF + 1)) + 1;
+//------------------------------------------------------------------------------
+int MinimumSerial::available() {
+  return UCSR0A & (1 << RXC0) ? 1 : 0;
+}
+//------------------------------------------------------------------------------
+void MinimumSerial::begin(uint32_t baud) {
+  uint16_t baud_setting;
+  // don't worry, the compiler will squeeze out F_CPU != 16000000UL
+  if ((F_CPU != 16000000UL || baud != 57600) && baud > MIN_2X_BAUD) {
+    // Double the USART Transmission Speed
+    UCSR0A = 1 << U2X0;
+    baud_setting = (F_CPU / 4 / baud - 1) / 2;
+  } else {
+    // hardcoded exception for compatibility with the bootloader shipped
+    // with the Duemilanove and previous boards and the firmware on the 8U2
+    // on the Uno and Mega 2560.
+    UCSR0A = 0;
+    baud_setting = (F_CPU / 8 / baud - 1) / 2;
+  }
+  // assign the baud_setting
+  UBRR0H = baud_setting >> 8;
+  UBRR0L = baud_setting;
+  // enable transmit and receive
+  UCSR0B |= (1 << TXEN0) | (1 << RXEN0);
+}
+//------------------------------------------------------------------------------
+void MinimumSerial::flush() {
+  while (((1 << UDRIE0) & UCSR0B) || !(UCSR0A & (1 << UDRE0))) {}
+}
+//------------------------------------------------------------------------------
+int MinimumSerial::read() {
+  if (UCSR0A & (1 << RXC0)) {
+    return UDR0;
+  }
+  return -1;
+}
+//------------------------------------------------------------------------------
+size_t MinimumSerial::write(uint8_t b) {
+  while (((1 << UDRIE0) & UCSR0B) || !(UCSR0A & (1 << UDRE0))) {}
+  UDR0 = b;
+  return 1;
+}
+#endif  //  defined(UDR0) || defined(DOXYGEN)

+ 67 - 0
lib/SdFat_NoArduino/src/MinimumSerial.h

@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+ /**
+ * \file
+ * \brief Minimal AVR Serial driver.
+ */
+#ifndef MinimumSerial_h
+#define MinimumSerial_h
+#include "common/SysCall.h"
+//==============================================================================
+/**
+ * \class MinimumSerial
+ * \brief mini serial class for the %SdFat library.
+ */
+class MinimumSerial : public print_t {
+ public:
+  /** \return true for hardware serial */
+  operator bool() {return true;}
+  /**
+   * \return one if data is available.
+   */
+  int available();
+  /**
+   * Set baud rate for serial port zero and enable in non interrupt mode.
+   * Do not call this function if you use another serial library.
+   * \param[in] baud rate
+   */
+  void begin(uint32_t baud);
+  /** Wait for write done. */
+  void flush();
+  /**
+   *  Unbuffered read
+   *  \return -1 if no character is available or an available character.
+   */
+  int read();
+  /**
+   * Unbuffered write
+   *
+   * \param[in] b byte to write.
+   * \return 1
+   */
+  size_t write(uint8_t b);
+  using print_t::write;
+};
+#endif  // MinimumSerial_h

+ 366 - 0
lib/SdFat_NoArduino/src/RingBuf.h

@@ -0,0 +1,366 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef RingBuf_h
+#define RingBuf_h
+/**
+ * \file
+ * \brief Ring buffer for data loggers.
+ */
+#include "common/SysCall.h"
+#include "common/FmtNumber.h"
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+//  Teensy 3.5/3.6 has hard fault at 0x20000000 for unaligned memcpy.
+#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
+inline bool is_aligned(const void* ptr, uintptr_t alignment) {
+    auto iptr = reinterpret_cast<uintptr_t>(ptr);
+    return !(iptr % alignment);
+}
+inline void memcpyBuf(void* dst, const void* src, size_t len) {
+  const uint8_t* b = reinterpret_cast<const uint8_t*>(0X20000000UL);
+  uint8_t* d = reinterpret_cast<uint8_t*>(dst);
+  const uint8_t *s = reinterpret_cast<const uint8_t*>(src);
+  if ((is_aligned(d, 4) && is_aligned(s, 4) && (len & 3) == 0) ||
+    !((d < b && b <= (d + len)) || (s < b && b <= (s + len)))) {
+    memcpy(dst, src, len);
+  } else {
+    while (len--) {
+      *d++ = *s++;
+    }
+  }
+}
+#else  // defined(__MK64FX512__) || defined(__MK66FX1M0__)
+inline void memcpyBuf(void* dst, const void* src, size_t len) {
+  memcpy(dst, src, len);
+}
+#endif  // defined(__MK64FX512__) || defined(__MK66FX1M0__)
+#endif  // DOXYGEN_SHOULD_SKIP_THIS
+/**
+ * \class RingBuf
+ * \brief Ring buffer for data loggers.
+ *
+ * This ring buffer may be used in ISRs.  bytesFreeIsr(), bytesUsedIsr(),
+ * memcopyIn(), and memcopyOut() are ISR callable.  For ISR use call
+ * memcopyIn() in the ISR and use writeOut() in non-interrupt code
+ * to write data to a file. readIn() and memcopyOut can be use in a
+ * similar way to provide file data to an ISR.
+ *
+ * Print into a RingBuf in an ISR should also work but has not been verified.
+ */
+template<class F, size_t Size>
+class RingBuf : public Print {
+ public:
+  /**
+   * RingBuf Constructor.
+   */
+  RingBuf() {}
+  /**
+   * Initialize RingBuf.
+   * \param[in] file Underlying file.
+   */
+  void begin(F* file) {
+    m_file = file;
+    m_count = 0;
+    m_head = 0;
+    m_tail = 0;
+    clearWriteError();
+  }
+  /**
+   *
+   * \return the RingBuf free space in bytes. Not ISR callable.
+   */
+  size_t bytesFree() const {
+    size_t count;
+    noInterrupts();
+    count = m_count;
+    interrupts();
+    return Size - count;
+  }
+  /**
+   * \return the RingBuf free space in bytes. ISR callable.
+   */
+  size_t bytesFreeIsr() const {
+    return Size - m_count;
+  }
+  /**
+   * \return the RingBuf used space in bytes. Not ISR callable.
+   */
+  size_t bytesUsed() const {
+    size_t count;
+    noInterrupts();
+    count = m_count;
+    interrupts();
+    return count;
+  }
+  /**
+   * \return the RingBuf used space in bytes.  ISR callable.
+   */
+  size_t bytesUsedIsr() const {
+    return m_count;
+  }
+  /**
+   * Copy data to the RingBuf from buf.
+   * The number of bytes copied may be less than count if
+   * count is greater than bytesFree.
+   *
+   * This function may be used in an ISR with writeOut()
+   * in non-interrupt code.
+   *
+   * \param[in] buf Location of data to be copied.
+   * \param[in] count number of bytes to be copied.
+   * \return Number of bytes actually copied.
+   */
+  size_t memcpyIn(const void* buf, size_t count) {
+    const uint8_t* src = (const uint8_t*)buf;
+    size_t n = Size - m_count;
+    if (count > n) {
+      count = n;
+    }
+    size_t nread = 0;
+    while (nread != count) {
+        n = minSize(Size - m_head, count - nread);
+        memcpyBuf(m_buf + m_head, src + nread, n);
+        m_head = advance(m_head, n);
+        nread += n;
+    }
+    m_count += nread;
+    return nread;
+  }
+  /**
+   * Copy date from the RingBuf to buf.
+   * The number of bytes copied may be less than count if
+   * bytesUsed is less than count.
+   *
+   * This function may be used in an ISR with readIn() in
+   * non-interrupt code.
+   *
+   * \param[out] buf Location to receive the data.
+   * \param[in] count number of bytes to be copied.
+   * \return Number of bytes actually copied.
+   */
+  size_t memcpyOut(void* buf, size_t count) {
+    uint8_t* dst = reinterpret_cast<uint8_t*>(buf);
+    size_t nwrite = 0;
+    size_t n = m_count;
+    if (count > n) {
+      count = n;
+    }
+    while (nwrite != count) {
+      n = minSize(Size - m_tail, count - nwrite);
+      memcpyBuf(dst + nwrite, m_buf + m_tail, n);
+      m_tail = advance(m_tail, n);
+      nwrite += n;
+    }
+    m_count -= nwrite;
+    return nwrite;
+  }
+  /** Print a number followed by a field terminator.
+   * \param[in] value The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \param[in] prec Number of digits after decimal point.
+   * \return The number of bytes written.
+   */
+  size_t printField(double value, char term, uint8_t prec = 2) {
+    char buf[24];
+    char* str = buf + sizeof(buf);
+    if (term) {
+      *--str = term;
+      if (term == '\n') {
+        *--str = '\r';
+      }
+    }
+    str = fmtDouble(str, value, prec, false);
+    return write(str, buf + sizeof(buf) - str);
+  }
+  /** Print a number followed by a field terminator.
+   * \param[in] value The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \param[in] prec Number of digits after decimal point.
+   * \return The number of bytes written or -1 if an error occurs.
+   */
+  size_t printField(float value, char term, uint8_t prec = 2) {
+    return printField(static_cast<double>(value), term, prec);
+  }
+  /** Print a number followed by a field terminator.
+   * \param[in] value The number to be printed.
+   * \param[in] term The field terminator.  Use '\\n' for CR LF.
+   * \return The number of bytes written or -1 if an error occurs.
+   */
+  template <typename Type>
+  size_t printField(Type value, char term) {
+    char sign = 0;
+    char buf[3*sizeof(Type) + 3];
+    char* str = buf + sizeof(buf);
+
+    if (term) {
+      *--str = term;
+      if (term == '\n') {
+        *--str = '\r';
+      }
+    }
+    if (value < 0) {
+      value = -value;
+      sign = '-';
+    }
+    if (sizeof(Type) < 4) {
+      str = fmtBase10(str, (uint16_t)value);
+    } else {
+      str = fmtBase10(str, (uint32_t)value);
+    }
+    if (sign) {
+      *--str = sign;
+    }
+    return write((const uint8_t*)str, &buf[sizeof(buf)] - str);
+  }
+  /**
+   * Read data into the RingBuf from the underlying file.
+   * the number of bytes read may be less than count if
+   * bytesFree is less than count.
+   *
+   * This function may be used in non-interrupt code with
+   * memcopyOut() in an ISR.
+   *
+   * \param[in] count number of bytes to be read.
+   * \return Number of bytes actually read.
+   */
+  size_t readIn(size_t count) {
+    size_t nread = 0;
+    size_t n = bytesFree();  // Protected from interrupts.
+    if (count > n) {
+      count = n;
+    }
+    while (nread != count) {
+        n = minSize(Size - m_head, count - nread);
+        if ((size_t)m_file->read(m_buf + m_head, n) != n) {
+          return nread;
+        }
+        m_head = advance(m_head, n);
+        nread += n;
+    }
+    noInterrupts();
+    m_count += nread;
+    interrupts();
+    return nread;
+  }
+  /**
+   * Write all data in the RingBuf to the underlying file.
+   * \return true for success.
+   */
+  bool sync() {
+    size_t n = bytesUsed();
+    return writeOut(n) == n;
+  }
+  /**
+   * Copy data to the RingBuf from buf.
+   *
+   * The number of bytes copied may be less than count if
+   * count is greater than bytesFree.
+   * Use getWriteError() to check for print errors and
+   * clearWriteError() to clear error.
+   *
+   * \param[in] buf Location of data to be written.
+   * \param[in] count number of bytes to be written.
+   * \return Number of bytes actually written.
+   */
+  size_t write(const void* buf, size_t count) {
+    if (count > bytesFree()) {
+      setWriteError();
+    }
+    return memcpyIn(buf, count);
+  }
+  /**
+   * Copy str to RingBuf.
+   *
+   * \param[in] str Location of data to be written.
+   * \return Number of bytes actually written.
+   */
+  size_t write(const char* str) {
+    return Print::write(str);
+  }
+  /**
+   * Override virtual function in Print for efficiency.
+   *
+   * \param[in] buf Location of data to be written.
+   * \param[in] count number of bytes to be written.
+   * \return Number of bytes actually written.
+   */
+  size_t write(const uint8_t* buf, size_t count) override {
+    return write((const void*)buf, count);
+  }
+  /**
+   * Required function for Print.
+   * \param[in] data Byte to be written.
+   * \return Number of bytes actually written.
+   */
+  size_t write(uint8_t data) override {
+    return write(&data, 1);
+  }
+  /**
+   * Write data to file from RingBuf buffer.
+   * \param[in] count number of bytes to be written.
+   *
+   * The number of bytes written may be less than count if
+   * bytesUsed is less than count or if an error occurs.
+   *
+   * This function may be used in non-interrupt code with
+   * memcopyIn() in an ISR.
+   *
+   * \return Number of bytes actually written.
+   */
+  size_t writeOut(size_t count) {
+    size_t n = bytesUsed();  // Protected from interrupts;
+     if (count > n) {
+      count = n;
+    }
+    size_t nwrite = 0;
+    while (nwrite != count) {
+      n = minSize(Size - m_tail, count - nwrite);
+      if (m_file->write(m_buf + m_tail, n) != n) {
+        break;
+      }
+      m_tail = advance(m_tail, n);
+      nwrite += n;
+    }
+    noInterrupts();
+    m_count -= nwrite;
+    interrupts();
+    return nwrite;
+  }
+
+ private:
+  uint8_t __attribute__((aligned(4))) m_buf[Size];
+  F* m_file = nullptr;
+  volatile size_t m_count;
+  size_t m_head;
+  size_t m_tail;
+
+  size_t advance(size_t index, size_t n) {
+    index += n;
+    return index < Size ? index : index - Size;
+  }
+  // avoid macro MIN
+  size_t minSize(size_t a, size_t b) {return a < b ? a : b;}
+};
+#endif  // RingBuf_h

+ 82 - 0
lib/SdFat_NoArduino/src/SdCard/SdCard.h

@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef SdCard_h
+#define SdCard_h
+#include "SdioCard.h"
+#include "SdSpiCard.h"
+#if HAS_SDIO_CLASS
+typedef SdCardInterface SdCard;
+#else  // HAS_SDIO_CLASS
+typedef SdSpiCard SdCard;
+#endif  // HAS_SDIO_CLASS
+/** Determine card configuration type.
+ *
+ * \param[in] cfg Card configuration.
+ * \return true if SPI.
+ */
+inline bool isSpi(SdSpiConfig cfg) {(void)cfg; return true;}
+/** Determine card configuration type.
+ *
+ * \param[in] cfg Card configuration.
+ * \return true if SPI.
+ */
+inline bool isSpi(SdioConfig cfg) {(void)cfg; return false;}
+/**
+ * \class SdCardFactory
+ * \brief Setup a SPI card or SDIO card.
+ */
+class SdCardFactory {
+ public:
+  /** Initialize SPI card.
+   *
+   * \param[in] config SPI configuration.
+   * \return generic card pointer.
+   */
+  SdCard* newCard(SdSpiConfig config) {
+    m_spiCard.begin(config);
+    return &m_spiCard;
+  }
+  /** Initialize SDIO card.
+   *
+   * \param[in] config SDIO configuration.
+   * \return generic card pointer or nullptr if SDIO is not supported.
+   */
+  SdCard* newCard(SdioConfig config) {
+#if HAS_SDIO_CLASS
+    m_sdioCard.begin(config);
+    return &m_sdioCard;
+#else  // HAS_SDIO_CLASS
+    (void)config;
+    return nullptr;
+#endif  // HAS_SDIO_CLASS
+  }
+
+ private:
+#if HAS_SDIO_CLASS
+  SdioCard m_sdioCard;
+#endif  // HAS_SDIO_CLASS
+  SdSpiCard m_spiCard;
+};
+#endif  // SdCard_h

+ 45 - 0
lib/SdFat_NoArduino/src/SdCard/SdCardInfo.cpp

@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "SdCardInfo.h"
+//------------------------------------------------------------------------------
+#undef SD_CARD_ERROR
+#define SD_CARD_ERROR(e, m) case SD_CARD_ERROR_##e: pr->print(F(#e)); break;
+void printSdErrorSymbol(print_t* pr, uint8_t code) {
+  pr->print(F("SD_CARD_ERROR_"));
+  switch (code) {
+    SD_ERROR_CODE_LIST
+    default: pr->print(F("UNKNOWN"));
+  }
+}
+//------------------------------------------------------------------------------
+#undef SD_CARD_ERROR
+#define SD_CARD_ERROR(e, m) case SD_CARD_ERROR_##e: pr->print(F(m)); break;
+void printSdErrorText(print_t* pr, uint8_t code) {
+  switch
+  (code) {
+    SD_ERROR_CODE_LIST
+    default: pr->print(F("Unknown error"));
+  }
+}

+ 420 - 0
lib/SdFat_NoArduino/src/SdCard/SdCardInfo.h

@@ -0,0 +1,420 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef SdCardInfo_h
+#define SdCardInfo_h
+#include <stdint.h>
+#include "../common/SysCall.h"
+// Based on the document:
+//
+// SD Specifications
+// Part 1
+// Physical Layer
+// Simplified Specification
+// Version 8.00
+// Sep 23, 2020
+//
+// https://www.sdcard.org/downloads/pls/
+#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
+// SD registers are big endian.
+#error bit fields in structures assume little endian processor.
+#endif  // __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
+//------------------------------------------------------------------------------
+// SD card errors
+// See the SD Specification for command info.
+#define SD_ERROR_CODE_LIST\
+  SD_CARD_ERROR(NONE, "No error")\
+  SD_CARD_ERROR(CMD0, "Card reset failed")\
+  SD_CARD_ERROR(CMD2, "SDIO read CID")\
+  SD_CARD_ERROR(CMD3, "SDIO publish RCA")\
+  SD_CARD_ERROR(CMD6, "Switch card function")\
+  SD_CARD_ERROR(CMD7, "SDIO card select")\
+  SD_CARD_ERROR(CMD8, "Send and check interface settings")\
+  SD_CARD_ERROR(CMD9, "Read CSD data")\
+  SD_CARD_ERROR(CMD10, "Read CID data")\
+  SD_CARD_ERROR(CMD12, "Stop multiple block read")\
+  SD_CARD_ERROR(CMD13, "Read card status")\
+  SD_CARD_ERROR(CMD17, "Read single block")\
+  SD_CARD_ERROR(CMD18, "Read multiple blocks")\
+  SD_CARD_ERROR(CMD24, "Write single block")\
+  SD_CARD_ERROR(CMD25, "Write multiple blocks")\
+  SD_CARD_ERROR(CMD32, "Set first erase block")\
+  SD_CARD_ERROR(CMD33, "Set last erase block")\
+  SD_CARD_ERROR(CMD38, "Erase selected blocks")\
+  SD_CARD_ERROR(CMD58, "Read OCR register")\
+  SD_CARD_ERROR(CMD59, "Set CRC mode")\
+  SD_CARD_ERROR(ACMD6, "Set SDIO bus width")\
+  SD_CARD_ERROR(ACMD13, "Read extended status")\
+  SD_CARD_ERROR(ACMD23, "Set pre-erased count")\
+  SD_CARD_ERROR(ACMD41, "Activate card initialization")\
+  SD_CARD_ERROR(ACMD51, "Read SCR data")\
+  SD_CARD_ERROR(READ_TOKEN, "Bad read data token")\
+  SD_CARD_ERROR(READ_CRC, "Read CRC error")\
+  SD_CARD_ERROR(READ_FIFO, "SDIO fifo read timeout")\
+  SD_CARD_ERROR(READ_REG, "Read CID or CSD failed.")\
+  SD_CARD_ERROR(READ_START, "Bad readStart argument")\
+  SD_CARD_ERROR(READ_TIMEOUT, "Read data timeout")\
+  SD_CARD_ERROR(STOP_TRAN, "Multiple block stop failed")\
+  SD_CARD_ERROR(TRANSFER_COMPLETE, "SDIO transfer complete")\
+  SD_CARD_ERROR(WRITE_DATA, "Write data not accepted")\
+  SD_CARD_ERROR(WRITE_FIFO, "SDIO fifo write timeout")\
+  SD_CARD_ERROR(WRITE_START, "Bad writeStart argument")\
+  SD_CARD_ERROR(WRITE_PROGRAMMING, "Flash programming")\
+  SD_CARD_ERROR(WRITE_TIMEOUT, "Write timeout")\
+  SD_CARD_ERROR(DMA, "DMA transfer failed")\
+  SD_CARD_ERROR(ERASE, "Card did not accept erase commands")\
+  SD_CARD_ERROR(ERASE_SINGLE_SECTOR, "Card does not support erase")\
+  SD_CARD_ERROR(ERASE_TIMEOUT, "Erase command timeout")\
+  SD_CARD_ERROR(INIT_NOT_CALLED, "Card has not been initialized")\
+  SD_CARD_ERROR(INVALID_CARD_CONFIG, "Invalid card config")\
+  SD_CARD_ERROR(FUNCTION_NOT_SUPPORTED, "Unsupported SDIO command")
+
+enum {
+#define SD_CARD_ERROR(e, m) SD_CARD_ERROR_##e,
+  SD_ERROR_CODE_LIST
+#undef SD_CARD_ERROR
+  SD_CARD_ERROR_UNKNOWN
+};
+void printSdErrorSymbol(print_t* pr, uint8_t code);
+void printSdErrorText(print_t* pr, uint8_t code);
+//------------------------------------------------------------------------------
+// card types
+/** Standard capacity V1 SD card */
+const uint8_t SD_CARD_TYPE_SD1  = 1;
+/** Standard capacity V2 SD card */
+const uint8_t SD_CARD_TYPE_SD2  = 2;
+/** High Capacity SD card */
+const uint8_t SD_CARD_TYPE_SDHC = 3;
+//------------------------------------------------------------------------------
+// SD operation timeouts
+/** CMD0 retry count */
+const uint8_t SD_CMD0_RETRY = 10;
+/** command timeout ms */
+const uint16_t SD_CMD_TIMEOUT = 300;
+/** erase timeout ms */
+const uint16_t SD_ERASE_TIMEOUT = 10000;
+/** init timeout ms */
+const uint16_t SD_INIT_TIMEOUT = 2000;
+/** read timeout ms */
+const uint16_t SD_READ_TIMEOUT = 300;
+/** write time out ms */
+const uint16_t SD_WRITE_TIMEOUT = 600;
+//------------------------------------------------------------------------------
+// SD card commands
+/** GO_IDLE_STATE - init card in spi mode if CS low */
+const uint8_t CMD0 = 0X00;
+/** ALL_SEND_CID - Asks any card to send the CID. */
+const uint8_t CMD2 = 0X02;
+/** SEND_RELATIVE_ADDR - Ask the card to publish a new RCA. */
+const uint8_t CMD3 = 0X03;
+/** SWITCH_FUNC - Switch Function Command */
+const uint8_t CMD6 = 0X06;
+/** SELECT/DESELECT_CARD - toggles between the stand-by and transfer states. */
+const uint8_t CMD7 = 0X07;
+/** SEND_IF_COND - verify SD Memory Card interface operating condition.*/
+const uint8_t CMD8 = 0X08;
+/** SEND_CSD - read the Card Specific Data (CSD register) */
+const uint8_t CMD9 = 0X09;
+/** SEND_CID - read the card identification information (CID register) */
+const uint8_t CMD10 = 0X0A;
+/** VOLTAGE_SWITCH -Switch to 1.8V bus signaling level. */
+const uint8_t CMD11 = 0X0B;
+/** STOP_TRANSMISSION - end multiple sector read sequence */
+const uint8_t CMD12 = 0X0C;
+/** SEND_STATUS - read the card status register */
+const uint8_t CMD13 = 0X0D;
+/** READ_SINGLE_SECTOR - read a single data sector from the card */
+const uint8_t CMD17 = 0X11;
+/** READ_MULTIPLE_SECTOR - read multiple data sectors from the card */
+const uint8_t CMD18 = 0X12;
+/** WRITE_SECTOR - write a single data sector to the card */
+const uint8_t CMD24 = 0X18;
+/** WRITE_MULTIPLE_SECTOR - write sectors of data until a STOP_TRANSMISSION */
+const uint8_t CMD25 = 0X19;
+/** ERASE_WR_BLK_START - sets the address of the first sector to be erased */
+const uint8_t CMD32 = 0X20;
+/** ERASE_WR_BLK_END - sets the address of the last sector of the continuous
+    range to be erased*/
+const uint8_t CMD33 = 0X21;
+/** ERASE - erase all previously selected sectors */
+const uint8_t CMD38 = 0X26;
+/** APP_CMD - escape for application specific command */
+const uint8_t CMD55 = 0X37;
+/** READ_OCR - read the OCR register of a card */
+const uint8_t CMD58 = 0X3A;
+/** CRC_ON_OFF - enable or disable CRC checking */
+const uint8_t CMD59 = 0X3B;
+/** SET_BUS_WIDTH - Defines the data bus width for data transfer. */
+const uint8_t ACMD6 = 0X06;
+/** SD_STATUS - Send the SD Status. */
+const uint8_t ACMD13 = 0X0D;
+/** SET_WR_BLK_ERASE_COUNT - Set the number of write sectors to be
+     pre-erased before writing */
+const uint8_t ACMD23 = 0X17;
+/** SD_SEND_OP_COMD - Sends host capacity support information and
+    activates the card's initialization process */
+const uint8_t ACMD41 = 0X29;
+/** Reads the SD Configuration Register (SCR). */
+const uint8_t ACMD51 = 0X33;
+//==============================================================================
+// CARD_STATUS
+/** The command's argument was out of the allowed range for this card. */
+const uint32_t CARD_STATUS_OUT_OF_RANGE = 1UL << 31;
+/** A misaligned address which did not match the sector length. */
+const uint32_t CARD_STATUS_ADDRESS_ERROR = 1UL << 30;
+/** The transferred sector length is not allowed for this card. */
+const uint32_t CARD_STATUS_SECTOR_LEN_ERROR = 1UL << 29;
+/** An error in the sequence of erase commands occurred. */
+const uint32_t CARD_STATUS_ERASE_SEQ_ERROR = 1UL <<28;
+/** An invalid selection of write-sectors for erase occurred. */
+const uint32_t CARD_STATUS_ERASE_PARAM = 1UL << 27;
+/** Set when the host attempts to write to a protected sector. */
+const uint32_t CARD_STATUS_WP_VIOLATION = 1UL << 26;
+/** When set, signals that the card is locked by the host. */
+const uint32_t CARD_STATUS_CARD_IS_LOCKED = 1UL << 25;
+/** Set when a sequence or password error has been detected. */
+const uint32_t CARD_STATUS_LOCK_UNLOCK_FAILED = 1UL << 24;
+/** The CRC check of the previous command failed. */
+const uint32_t CARD_STATUS_COM_CRC_ERROR = 1UL << 23;
+/** Command not legal for the card state. */
+const uint32_t CARD_STATUS_ILLEGAL_COMMAND = 1UL << 22;
+/** Card internal ECC was applied but failed to correct the data. */
+const uint32_t CARD_STATUS_CARD_ECC_FAILED = 1UL << 21;
+/** Internal card controller error */
+const uint32_t CARD_STATUS_CC_ERROR = 1UL << 20;
+/** A general or an unknown error occurred during the operation. */
+const uint32_t CARD_STATUS_ERROR = 1UL << 19;
+// bits 19, 18, and 17 reserved.
+/** Permanent WP set or attempt to change read only values of  CSD. */
+const uint32_t CARD_STATUS_CSD_OVERWRITE = 1UL <<16;
+/** partial address space was erased due to write protect. */
+const uint32_t CARD_STATUS_WP_ERASE_SKIP = 1UL << 15;
+/** The command has been executed without using the internal ECC. */
+const uint32_t CARD_STATUS_CARD_ECC_DISABLED = 1UL << 14;
+/** out of erase sequence command was received. */
+const uint32_t CARD_STATUS_ERASE_RESET = 1UL << 13;
+/** The state of the card when receiving the command.
+ * 0 = idle
+ * 1 = ready
+ * 2 = ident
+ * 3 = stby
+ * 4 = tran
+ * 5 = data
+ * 6 = rcv
+ * 7 = prg
+ * 8 = dis
+ * 9-14 = reserved
+ * 15 = reserved for I/O mode
+ */
+const uint32_t CARD_STATUS_CURRENT_STATE = 0XF << 9;
+/** Shift for current state. */
+const uint32_t CARD_STATUS_CURRENT_STATE_SHIFT = 9;
+/** Corresponds to buffer empty signaling on the bus. */
+const uint32_t CARD_STATUS_READY_FOR_DATA = 1UL << 8;
+// bit 7 reserved.
+/** Extension Functions may set this bit to get host to deal with events. */
+const uint32_t CARD_STATUS_FX_EVENT = 1UL << 6;
+/** The card will expect ACMD, or the command has been interpreted as ACMD */
+const uint32_t CARD_STATUS_APP_CMD = 1UL << 5;
+// bit 4 reserved.
+/** Error in the sequence of the authentication process. */
+const uint32_t CARD_STATUS_AKE_SEQ_ERROR = 1UL << 3;
+// bits 2,1, and 0 reserved for manufacturer test mode.
+//==============================================================================
+/** status for card in the ready state */
+const uint8_t R1_READY_STATE = 0X00;
+/** status for card in the idle state */
+const uint8_t R1_IDLE_STATE = 0X01;
+/** status bit for illegal command */
+const uint8_t R1_ILLEGAL_COMMAND = 0X04;
+/** start data token for read or write single sector*/
+const uint8_t DATA_START_SECTOR = 0XFE;
+/** stop token for write multiple sectors*/
+const uint8_t STOP_TRAN_TOKEN = 0XFD;
+/** start data token for write multiple sectors*/
+const uint8_t WRITE_MULTIPLE_TOKEN = 0XFC;
+/** mask for data response tokens after a write sector operation */
+const uint8_t DATA_RES_MASK = 0X1F;
+/** write data accepted token */
+const uint8_t DATA_RES_ACCEPTED = 0X05;
+//==============================================================================
+/**
+ * \class CID
+ * \brief Card IDentification (CID) register.
+ */
+typedef struct CID {
+  // byte 0
+  /** Manufacturer ID */
+  uint8_t mid;
+  // byte 1-2
+  /** OEM/Application ID. */
+  char oid[2];
+  // byte 3-7
+  /** Product name. */
+  char pnm[5];
+  // byte 8
+  /** Product revision - n.m two 4-bit nibbles. */
+  uint8_t prv;
+  // byte 9-12
+  /** Product serial 32-bit number Big Endian format. */
+  uint8_t psn8[4];
+  // byte 13-14
+  /** Manufacturing date big endian - four nibbles RYYM Reserved Year Month. */
+  uint8_t mdt[2];
+  // byte 15
+  /** CRC7 bits 1-7 checksum, bit 0 always 1 */
+  uint8_t crc;
+  // Extract big endian fields.
+  /** \return major revision number. */
+  int prvN() const {return prv >> 4;}
+  /** \return minor revision number. */
+  int prvM() const {return prv & 0XF;}
+  /** \return Manufacturing Year. */
+  int mdtYear() const {return 2000 + ((mdt[0] & 0XF) << 4) + (mdt[1] >> 4);}
+  /** \return Manufacturing Month. */
+  int mdtMonth() const {return mdt[1] & 0XF;}
+  /** \return Product Serial Number. */
+  uint32_t psn() const {
+  return (uint32_t)psn8[0] << 24 |
+         (uint32_t)psn8[1] << 16 |
+         (uint32_t)psn8[2] <<  8 |
+         (uint32_t)psn8[3];
+  }
+} __attribute__((packed)) cid_t;
+//==============================================================================
+/**
+ * \class CSD
+ * \brief Union of old and new style CSD register.
+ */
+typedef struct CSD {
+  /** union of all CSD versions */
+  uint8_t csd[16];
+  // Extract big endian fields.
+  /** \return Capacity in sectors */
+  uint32_t capacity() const {
+    uint32_t c_size;
+    uint8_t ver = csd[0] >> 6;
+    if (ver == 0) {
+      c_size = (uint32_t)(csd[6] & 3) << 10;
+      c_size |= (uint32_t)csd[7] << 2 | csd[8] >> 6;
+      uint8_t c_size_mult = (csd[9] & 3) << 1 | csd[10] >> 7;
+      uint8_t read_bl_len = csd[5] & 15;
+      return (c_size + 1) << (c_size_mult + read_bl_len + 2 - 9);
+    } else if (ver == 1) {
+      c_size = (uint32_t)(csd[7] & 63) << 16;
+      c_size |= (uint32_t)csd[8] << 8;
+      c_size |= csd[9];
+      return (c_size + 1) << 10;
+    } else {
+      return 0;
+    }
+  }
+  /** \return true if erase granularity is single block. */
+  bool eraseSingleBlock() const {return csd[10] & 0X40;}
+  /** \return erase size in 512 byte blocks if eraseSingleBlock is false. */
+  int eraseSize() const {return ((csd[10] & 0X3F) << 1 | csd[11] >> 7) + 1;}
+  /** \return true if the contents is copied or true if original. */
+  bool copy() const {return csd[14] & 0X40;}
+  /** \return true if the entire card is permanently write protected. */
+  bool permWriteProtect() const {return  csd[14] & 0X20;}
+  /** \return true if the entire card is temporarily write protected. */
+  bool tempWriteProtect() const {return  csd[14] & 0X10;}
+} csd_t;
+//==============================================================================
+/**
+ * \class SCR
+ * \brief SCR register.
+ */
+typedef struct SCR {
+  /** Bytes 0-3 SD Association, bytes 4-7 reserved for manufacturer. */
+  uint8_t scr[8];
+  /** \return SCR_STRUCTURE field  - must be zero.*/
+  uint8_t srcStructure() {return scr[0] >> 4;}
+  /** \return SD_SPEC field 0 - v1.0 or V1.01, 1 - 1.10, 2 - V2.00 or greater */
+  uint8_t sdSpec() {return scr[0] & 0XF;}
+  /** \return false if all zero, true if all one. */
+  bool dataAfterErase() {return 0X80 & scr[1];}
+  /** \return CPRM Security Version. */
+  uint8_t sdSecurity() {return (scr[1] >> 4) & 0X7;}
+  /** \return 0101b.  */
+  uint8_t sdBusWidths() {return scr[1] & 0XF;}
+  /** \return true if V3.0 or greater. */
+  bool sdSpec3() {return scr[2] & 0X80;}
+  /** \return if true and sdSpecX is zero V4.xx. */
+  bool sdSpec4() {return scr[2] & 0X4;}
+  /** \return nonzero for version 5 or greater if sdSpec == 2,
+              sdSpec3 == true. Version is return plus four.*/
+  uint8_t sdSpecX() {return (scr[2] & 0X3) << 2 | scr[3] >> 6;}
+  /** \return bit map for support CMD58/59, CMD48/49, CMD23, and CMD20 */
+  uint8_t cmdSupport() {return scr[3] &0XF;}
+  /** \return SD spec version */
+  int16_t sdSpecVer() {
+    if (sdSpec() > 2) {
+      return -1;
+    } else if (sdSpec() < 2) {
+      return sdSpec() ? 110 : 101;
+    } else if (!sdSpec3()) {
+      return 200;
+    } else if (!sdSpec4() && !sdSpecX()) {
+      return 300;
+    }
+    return 400 + 100*sdSpecX();
+  }
+} scr_t;
+//==============================================================================
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+// fields are big endian
+typedef struct SdStatus {
+  //
+  uint8_t busWidthSecureMode;
+  uint8_t reserved1;
+  // byte 2
+  uint8_t sdCardType[2];
+  // byte 4
+  uint8_t sizeOfProtectedArea[4];
+  // byte 8
+  uint8_t speedClass;
+  // byte 9
+  uint8_t performanceMove;
+  // byte 10
+  uint8_t auSize;
+  // byte 11
+  uint8_t eraseSize[2];
+  // byte 13
+  uint8_t eraseTimeoutOffset;
+  // byte 14
+  uint8_t uhsSpeedAuSize;
+  // byte 15
+  uint8_t videoSpeed;
+  // byte 16
+  uint8_t vscAuSize[2];
+  // byte 18
+  uint8_t susAddr[3];
+  // byte 21
+  uint8_t reserved2[3];
+  // byte 24
+  uint8_t reservedManufacturer[40];
+} SdStatus_t;
+#endif  // DOXYGEN_SHOULD_SKIP_THIS
+#endif  // SdCardInfo_h

+ 129 - 0
lib/SdFat_NoArduino/src/SdCard/SdCardInterface.h

@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef SdCardInterface_h
+#define SdCardInterface_h
+#include "../common/FsBlockDeviceInterface.h"
+#include "SdCardInfo.h"
+/**
+ * \class SdCardInterface
+ * \brief Abstract interface for an SD card.
+ */
+class SdCardInterface : public FsBlockDeviceInterface {
+ public:
+  /** CMD6 Switch mode: Check Function Set Function.
+   * \param[in] arg CMD6 argument.
+   * \param[out] status return status data.
+   *
+   * \return true for success or false for failure.
+   */
+  virtual bool cardCMD6(uint32_t arg, uint8_t* status) = 0;
+  /** end use of card */
+  virtual void end() = 0;
+   /** Erase a range of sectors.
+   *
+   * \param[in] firstSector The address of the first sector in the range.
+   * \param[in] lastSector The address of the last sector in the range.
+   *
+   * \return true for success or false for failure.
+   */
+  virtual bool erase(uint32_t firstSector, uint32_t lastSector) = 0;
+  /** \return error code. */
+  virtual uint8_t errorCode() const = 0;
+  /** \return error data. */
+  virtual uint32_t errorData() const = 0;
+  /** \return true if card is busy. */
+  virtual bool isBusy() = 0;
+  /** \return false by default */
+  virtual bool hasDedicatedSpi() {return false;}
+  /** \return false by default */
+  bool virtual isDedicatedSpi() {return false;}
+  /** Set SPI sharing state
+   * \param[in] value desired state.
+   * \return false by default.
+   */
+  virtual bool setDedicatedSpi(bool value) {
+    (void)value;
+    return false;
+  }
+  /**
+   * Read a card's CID register.
+   *
+   * \param[out] cid pointer to area for returned data.
+   *
+   * \return true for success or false for failure.
+   */
+  virtual bool readCID(cid_t* cid) = 0;
+   /**
+   * Read a card's CSD register.
+   *
+   * \param[out] csd pointer to area for returned data.
+   *
+   * \return true for success or false for failure.
+   */
+  virtual bool readCSD(csd_t* csd) = 0;
+  /** Read OCR register.
+   *
+   * \param[out] ocr Value of OCR register.
+   * \return true for success or false for failure.
+   */
+  virtual bool readOCR(uint32_t* ocr) = 0;
+  /** Read SCR register.
+   *
+   * \param[out] scr Value of SCR register.
+   * \return true for success or false for failure.
+   */
+  virtual bool readSCR(scr_t *scr) = 0;
+  /**
+   * Determine the size of an SD flash memory card.
+   *
+   * \return The number of 512 byte data sectors in the card
+   *         or zero if an error occurs.
+   */
+  virtual uint32_t sectorCount() = 0;
+  /** \return card status. */
+  virtual uint32_t status() {return 0XFFFFFFFF;}
+  /** Return the card type: SD V1, SD V2 or SDHC/SDXC
+   * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC/SDXC.
+   */
+  virtual uint8_t type() const = 0;
+  /** Write one data sector in a multiple sector write sequence.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \return true for success or false for failure.
+   */
+
+  virtual bool writeData(const uint8_t* src) = 0;
+  /** Start a write multiple sectors sequence.
+   *
+   * \param[in] sector Address of first sector in sequence.
+   *
+   * \return true for success or false for failure.
+   */
+  virtual bool writeStart(uint32_t sector) = 0;
+  /** End a write multiple sectors sequence.
+   * \return true for success or false for failure.
+   */
+  virtual bool writeStop() = 0;
+};
+#endif  // SdCardInterface_h

+ 777 - 0
lib/SdFat_NoArduino/src/SdCard/SdSpiCard.cpp

@@ -0,0 +1,777 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "SdSpiCard.h"
+//==============================================================================
+class Timeout {
+ public:
+  Timeout() {}
+  explicit Timeout(uint16_t ms) {set(ms);}
+  uint16_t millis16() {return millis();}
+  void set(uint16_t ms) {
+    m_endTime = ms + millis16();
+  }
+  bool timedOut() {
+    return (int16_t)(m_endTime - millis16()) < 0;
+  }
+ private:
+  uint16_t m_endTime;
+};
+//==============================================================================
+#if USE_SD_CRC
+// CRC functions
+//------------------------------------------------------------------------------
+static uint8_t CRC7(const uint8_t* data, uint8_t n) {
+  uint8_t crc = 0;
+  for (uint8_t i = 0; i < n; i++) {
+    uint8_t d = data[i];
+    for (uint8_t j = 0; j < 8; j++) {
+      crc <<= 1;
+      if ((d & 0x80) ^ (crc & 0x80)) {
+        crc ^= 0x09;
+      }
+      d <<= 1;
+    }
+  }
+  return (crc << 1) | 1;
+}
+//------------------------------------------------------------------------------
+#if USE_SD_CRC == 1
+// Shift based CRC-CCITT
+// uses the x^16,x^12,x^5,x^1 polynomial.
+static uint16_t CRC_CCITT(const uint8_t* data, size_t n) {
+  uint16_t crc = 0;
+  for (size_t i = 0; i < n; i++) {
+    crc = (uint8_t)(crc >> 8) | (crc << 8);
+    crc ^= data[i];
+    crc ^= (uint8_t)(crc & 0xff) >> 4;
+    crc ^= crc << 12;
+    crc ^= (crc & 0xff) << 5;
+  }
+  return crc;
+}
+#elif USE_SD_CRC > 1  // CRC_CCITT
+//------------------------------------------------------------------------------
+// Table based CRC-CCITT
+// uses the x^16,x^12,x^5,x^1 polynomial.
+#ifdef __AVR__
+static const uint16_t crctab[] PROGMEM = {
+#else  // __AVR__
+static const uint16_t crctab[] = {
+#endif  // __AVR__
+  0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
+  0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
+  0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
+  0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
+  0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
+  0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
+  0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
+  0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
+  0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
+  0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
+  0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
+  0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
+  0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
+  0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
+  0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
+  0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
+  0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
+  0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
+  0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
+  0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
+  0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
+  0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+  0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
+  0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
+  0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
+  0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
+  0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
+  0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
+  0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
+  0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
+  0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
+  0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
+};
+static uint16_t CRC_CCITT(const uint8_t* data, size_t n) {
+  uint16_t crc = 0;
+  for (size_t i = 0; i < n; i++) {
+#ifdef __AVR__
+    crc = pgm_read_word(&crctab[(crc >> 8 ^ data[i]) & 0XFF]) ^ (crc << 8);
+#else  // __AVR__
+    crc = crctab[(crc >> 8 ^ data[i]) & 0XFF] ^ (crc << 8);
+#endif  // __AVR__
+  }
+  return crc;
+}
+#endif  // CRC_CCITT
+#endif  // USE_SD_CRC
+//==============================================================================
+// SharedSpiCard member functions
+//------------------------------------------------------------------------------
+bool SharedSpiCard::begin(SdSpiConfig spiConfig) {
+  Timeout timeout;
+  m_spiActive = false;
+  m_beginCalled = false;
+  m_errorCode = SD_CARD_ERROR_NONE;
+  m_type = 0;
+  m_csPin = spiConfig.csPin;
+#if SPI_DRIVER_SELECT >= 2
+  m_spiDriverPtr = spiConfig.spiPort;
+  if (!m_spiDriverPtr) {
+    error(SD_CARD_ERROR_INVALID_CARD_CONFIG);
+    goto fail;
+  }
+#endif  // SPI_DRIVER_SELECT
+  sdCsInit(m_csPin);
+  spiUnselect();
+  spiSetSckSpeed(1000UL*SD_MAX_INIT_RATE_KHZ);
+  spiBegin(spiConfig);
+  m_beginCalled = true;
+  uint32_t arg;
+  m_state = IDLE_STATE;
+  spiStart();
+
+  // must supply min of 74 clock cycles with CS high.
+  spiUnselect();
+  for (uint8_t i = 0; i < 10; i++) {
+    spiSend(0XFF);
+  }
+  spiSelect();
+  // command to go idle in SPI mode
+  for (uint8_t i = 1;; i++) {
+    if (cardCommand(CMD0, 0) == R1_IDLE_STATE) {
+      break;
+    }
+    if (i == SD_CMD0_RETRY) {
+      error(SD_CARD_ERROR_CMD0);
+      goto fail;
+    }
+    // Force any active transfer to end for an already initialized card.
+    for (uint8_t j = 0; j < 0XFF; j++) {
+      spiSend(0XFF);
+    }
+  }
+#if USE_SD_CRC
+  if (cardCommand(CMD59, 1) != R1_IDLE_STATE) {
+    error(SD_CARD_ERROR_CMD59);
+    goto fail;
+  }
+#endif  // USE_SD_CRC
+  // check SD version
+  if (!(cardCommand(CMD8, 0x1AA) & R1_ILLEGAL_COMMAND)) {
+    type(SD_CARD_TYPE_SD2);
+    for (uint8_t i = 0; i < 4; i++) {
+      m_status = spiReceive();
+    }
+    if (m_status != 0XAA) {
+      error(SD_CARD_ERROR_CMD8);
+      goto fail;
+    }
+  } else {
+    type(SD_CARD_TYPE_SD1);
+  }
+  // initialize card and send host supports SDHC if SD2
+  arg = type() == SD_CARD_TYPE_SD2 ? 0X40000000 : 0;
+  timeout.set(SD_INIT_TIMEOUT);
+  while (cardAcmd(ACMD41, arg) != R1_READY_STATE) {
+    // check for timeout
+    if (timeout.timedOut()) {
+      error(SD_CARD_ERROR_ACMD41);
+      goto fail;
+    }
+  }
+
+  // if SD2 read OCR register to check for SDHC card
+  if (type() == SD_CARD_TYPE_SD2) {
+    if (cardCommand(CMD58, 0)) {
+      error(SD_CARD_ERROR_CMD58);
+      goto fail;
+    }
+    if ((spiReceive() & 0XC0) == 0XC0) {
+      type(SD_CARD_TYPE_SDHC);
+    }
+    // Discard rest of ocr - contains allowed voltage range.
+    for (uint8_t i = 0; i < 3; i++) {
+      spiReceive();
+    }
+  }
+  spiStop();
+  spiSetSckSpeed(spiConfig.maxSck);
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::cardCMD6(uint32_t arg, uint8_t* status) {
+  if (cardCommand(CMD6, arg)) {
+    error(SD_CARD_ERROR_CMD6);
+    goto fail;
+  }
+  if (!readData(status, 64)) {
+    goto fail;
+  }
+  spiStop();
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+// send command and return error code.  Return zero for OK
+uint8_t SharedSpiCard::cardCommand(uint8_t cmd, uint32_t arg) {
+  if (!syncDevice()) {
+    return 0XFF;
+  }
+  // select card
+  if (!m_spiActive) {
+    spiStart();
+  }
+  if (cmd != CMD0 && cmd != CMD12 && !waitReady(SD_CMD_TIMEOUT)) {
+    return 0XFF;
+  }
+#if USE_SD_CRC
+  // form message
+  uint8_t buf[6];
+  buf[0] = (uint8_t)0x40U | cmd;
+  buf[1] = (uint8_t)(arg >> 24U);
+  buf[2] = (uint8_t)(arg >> 16U);
+  buf[3] = (uint8_t)(arg >> 8U);
+  buf[4] = (uint8_t)arg;
+
+  // add CRC
+  buf[5] = CRC7(buf, 5);
+
+  // send message
+  spiSend(buf, 6);
+#else  // USE_SD_CRC
+  // send command
+  spiSend(cmd | 0x40);
+
+  // send argument
+  uint8_t* pa = reinterpret_cast<uint8_t*>(&arg);
+  for (int8_t i = 3; i >= 0; i--) {
+    spiSend(pa[i]);
+  }
+
+  // send CRC - correct for CMD0 with arg zero or CMD8 with arg 0X1AA
+  spiSend(cmd == CMD0 ? 0X95 : 0X87);
+#endif  // USE_SD_CRC
+
+  // discard first fill byte to avoid MISO pull-up problem.
+  spiReceive();
+
+  // there are 1-8 fill bytes before response.  fill bytes should be 0XFF.
+  uint16_t n = 0;
+  do {
+    m_status = spiReceive();
+  } while (m_status & 0X80 && ++n < 10);
+  return m_status;
+}
+//------------------------------------------------------------------------------
+void SharedSpiCard::end() {
+  if (m_beginCalled) {
+    spiStop();
+    spiEnd();
+    m_beginCalled = false;
+  }
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::erase(uint32_t firstSector, uint32_t lastSector) {
+  csd_t csd;
+  if (!readCSD(&csd)) {
+    goto fail;
+  }
+  // check for single sector erase
+  if (!csd.eraseSingleBlock()) {
+    // erase size mask
+    uint8_t m = csd.eraseSize() - 1;
+    if ((firstSector & m) != 0 || ((lastSector + 1) & m) != 0) {
+      // error card can't erase specified area
+      error(SD_CARD_ERROR_ERASE_SINGLE_SECTOR);
+      goto fail;
+    }
+  }
+  if (m_type != SD_CARD_TYPE_SDHC) {
+    firstSector <<= 9;
+    lastSector <<= 9;
+  }
+  if (cardCommand(CMD32, firstSector)
+      || cardCommand(CMD33, lastSector)
+      || cardCommand(CMD38, 0)) {
+    error(SD_CARD_ERROR_ERASE);
+    goto fail;
+  }
+  if (!waitReady(SD_ERASE_TIMEOUT)) {
+    error(SD_CARD_ERROR_ERASE_TIMEOUT);
+    goto fail;
+  }
+  spiStop();
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::eraseSingleSectorEnable() {
+  csd_t csd;
+  return readCSD(&csd) ? csd.eraseSingleBlock() : false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::isBusy() {
+  if (m_state == READ_STATE) {
+    return false;
+  }
+  bool spiActive = m_spiActive;
+  if (!spiActive) {
+    spiStart();
+  }
+  bool rtn = 0XFF != spiReceive();
+  if (!spiActive) {
+    spiStop();
+  }
+  return rtn;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::readData(uint8_t* dst) {
+  return readData(dst, 512);
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::readData(uint8_t* dst, size_t count) {
+#if USE_SD_CRC
+  uint16_t crc;
+#endif  // USE_SD_CRC
+
+  // wait for start sector token
+  Timeout timeout(SD_READ_TIMEOUT);
+  while ((m_status = spiReceive()) == 0XFF) {
+    if (timeout.timedOut()) {
+      error(SD_CARD_ERROR_READ_TIMEOUT);
+      goto fail;
+    }
+  }
+  if (m_status != DATA_START_SECTOR) {
+    error(SD_CARD_ERROR_READ_TOKEN);
+    goto fail;
+  }
+  // transfer data
+  if ((m_status = spiReceive(dst, count))) {
+    error(SD_CARD_ERROR_DMA);
+    goto fail;
+  }
+
+#if USE_SD_CRC
+  // get crc
+  crc = (spiReceive() << 8) | spiReceive();
+  if (crc != CRC_CCITT(dst, count)) {
+    error(SD_CARD_ERROR_READ_CRC);
+    goto fail;
+  }
+#else  // USE_SD_CRC
+  // discard crc
+  spiReceive();
+  spiReceive();
+#endif  // USE_SD_CRC
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::readOCR(uint32_t* ocr) {
+  uint8_t* p = reinterpret_cast<uint8_t*>(ocr);
+  if (cardCommand(CMD58, 0)) {
+    error(SD_CARD_ERROR_CMD58);
+    goto fail;
+  }
+  for (uint8_t i = 0; i < 4; i++) {
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+    p[3 - i] = spiReceive();
+#else  // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+    p[i] = spiReceive();
+#endif  // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+  }
+  spiStop();
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+/** read CID or CSR register */
+bool SharedSpiCard::readRegister(uint8_t cmd, void* buf) {
+  uint8_t* dst = reinterpret_cast<uint8_t*>(buf);
+  if (cardCommand(cmd, 0)) {
+    error(SD_CARD_ERROR_READ_REG);
+    goto fail;
+  }
+  if (!readData(dst, 16)) {
+    goto fail;
+  }
+  spiStop();
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::readSCR(scr_t* scr) {
+  uint8_t* dst = reinterpret_cast<uint8_t*>(scr);
+  if (cardAcmd(ACMD51, 0)) {
+    error(SD_CARD_ERROR_ACMD51);
+    goto fail;
+  }
+  if (!readData(dst, sizeof(scr_t))) {
+    goto fail;
+  }
+  spiStop();
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::readSector(uint32_t sector, uint8_t* dst) {
+  // use address if not SDHC card
+  if (type() != SD_CARD_TYPE_SDHC) {
+    sector <<= 9;
+  }
+  if (cardCommand(CMD17, sector)) {
+    error(SD_CARD_ERROR_CMD17);
+    goto fail;
+  }
+  if (!readData(dst, 512)) {
+    goto fail;
+  }
+  spiStop();
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::readSectors(uint32_t sector, uint8_t* dst, size_t ns) {
+  if (!readStart(sector)) {
+    goto fail;
+  }
+  for (size_t i = 0; i < ns; i++, dst += 512) {
+    if (!readData(dst, 512)) {
+      goto fail;
+    }
+  }
+  return readStop();
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::readStart(uint32_t sector) {
+  if (type() != SD_CARD_TYPE_SDHC) {
+    sector <<= 9;
+  }
+  if (cardCommand(CMD18, sector)) {
+    error(SD_CARD_ERROR_CMD18);
+    goto fail;
+  }
+  m_state = READ_STATE;
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::readStatus(SdStatus* status) {
+  uint8_t* dst = reinterpret_cast<uint8_t*>(status);
+  // retrun is R2 so read extra status byte.
+  if (cardAcmd(ACMD13, 0) || spiReceive()) {
+    error(SD_CARD_ERROR_ACMD13);
+    goto fail;
+  }
+  if (!readData(dst, 64)) {
+    goto fail;
+  }
+  spiStop();
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::readStop() {
+  m_state = IDLE_STATE;
+  if (cardCommand(CMD12, 0)) {
+    error(SD_CARD_ERROR_CMD12);
+    goto fail;
+  }
+  spiStop();
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+uint32_t SharedSpiCard::sectorCount() {
+  csd_t csd;
+  return readCSD(&csd) ? csd.capacity() : 0;
+}
+//------------------------------------------------------------------------------
+void SharedSpiCard::spiStart() {
+  if (!m_spiActive) {
+    spiActivate();
+    m_spiActive = true;
+    spiSelect();
+    // Dummy byte to drive MISO busy status.
+    spiSend(0XFF);
+  }
+}
+//------------------------------------------------------------------------------
+void SharedSpiCard::spiStop() {
+  if (m_spiActive) {
+    spiUnselect();
+    // Insure MISO goes to low Z.
+    spiSend(0XFF);
+    spiDeactivate();
+    m_spiActive = false;
+  }
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::syncDevice() {
+  if (m_state == WRITE_STATE) {
+    return writeStop();
+  }
+  if (m_state == READ_STATE) {
+    return readStop();
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::waitReady(uint16_t ms) {
+  Timeout timeout(ms);
+  while (spiReceive() != 0XFF) {
+    if (timeout.timedOut()) {
+      return false;
+    }
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::writeData(const uint8_t* src) {
+  // wait for previous write to finish
+  if (!waitReady(SD_WRITE_TIMEOUT)) {
+    error(SD_CARD_ERROR_WRITE_TIMEOUT);
+    goto fail;
+  }
+  if (!writeData(WRITE_MULTIPLE_TOKEN, src)) {
+    goto fail;
+  }
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+// send one sector of data for write sector or write multiple sectors
+bool SharedSpiCard::writeData(uint8_t token, const uint8_t* src) {
+#if USE_SD_CRC
+  uint16_t crc = CRC_CCITT(src, 512);
+#else  // USE_SD_CRC
+  uint16_t crc = 0XFFFF;
+#endif  // USE_SD_CRC
+  spiSend(token);
+  spiSend(src, 512);
+  spiSend(crc >> 8);
+  spiSend(crc & 0XFF);
+
+  m_status = spiReceive();
+  if ((m_status & DATA_RES_MASK) != DATA_RES_ACCEPTED) {
+    error(SD_CARD_ERROR_WRITE_DATA);
+    goto fail;
+  }
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::writeSector(uint32_t sector, const uint8_t* src) {
+  // use address if not SDHC card
+  if (type() != SD_CARD_TYPE_SDHC) {
+    sector <<= 9;
+  }
+  if (cardCommand(CMD24, sector)) {
+    error(SD_CARD_ERROR_CMD24);
+    goto fail;
+  }
+  if (!writeData(DATA_START_SECTOR, src)) {
+    goto fail;
+  }
+
+#if CHECK_FLASH_PROGRAMMING
+  // wait for flash programming to complete
+  if (!waitReady(SD_WRITE_TIMEOUT)) {
+    error(SD_CARD_ERROR_WRITE_PROGRAMMING);
+    goto fail;
+  }
+  // response is r2 so get and check two bytes for nonzero
+  if (cardCommand(CMD13, 0) || spiReceive()) {
+    error(SD_CARD_ERROR_CMD13);
+    goto fail;
+  }
+#endif  // CHECK_FLASH_PROGRAMMING
+
+  spiStop();
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::writeSectors(uint32_t sector,
+                                 const uint8_t* src, size_t ns) {
+  if (!writeStart(sector)) {
+    goto fail;
+  }
+  for (size_t i = 0; i < ns; i++, src += 512) {
+    if (!writeData(src)) {
+      goto fail;
+    }
+  }
+  return writeStop();
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::writeStart(uint32_t sector) {
+  // use address if not SDHC card
+  if (type() != SD_CARD_TYPE_SDHC) {
+    sector <<= 9;
+  }
+  if (cardCommand(CMD25, sector)) {
+    error(SD_CARD_ERROR_CMD25);
+    goto fail;
+  }
+  m_state = WRITE_STATE;
+  return true;
+
+ fail:
+  spiStop();
+  return false;
+}
+//------------------------------------------------------------------------------
+bool SharedSpiCard::writeStop() {
+  if (!waitReady(SD_WRITE_TIMEOUT)) {
+    goto fail;
+  }
+  spiSend(STOP_TRAN_TOKEN);
+  spiStop();
+  m_state = IDLE_STATE;
+  return true;
+
+ fail:
+  error(SD_CARD_ERROR_STOP_TRAN);
+  spiStop();
+  return false;
+}
+//==============================================================================
+bool DedicatedSpiCard::begin(SdSpiConfig spiConfig) {
+  if (!SharedSpiCard::begin(spiConfig)) {
+    return false;
+  }
+  m_dedicatedSpi = spiOptionDedicated(spiConfig.options);
+  return true;
+}
+//------------------------------------------------------------------------------
+bool DedicatedSpiCard::readSector(uint32_t sector, uint8_t* dst) {
+  return readSectors(sector, dst, 1);
+}
+//------------------------------------------------------------------------------
+bool DedicatedSpiCard::readSectors(
+    uint32_t sector, uint8_t* dst, size_t ns) {
+  if (sdState() != READ_STATE || sector != m_curSector) {
+    if (!readStart(sector)) {
+      goto fail;
+    }
+    m_curSector = sector;
+  }
+  for (size_t i = 0; i < ns; i++, dst += 512) {
+    if (!readData(dst)) {
+      goto fail;
+    }
+  }
+  m_curSector += ns;
+  return m_dedicatedSpi ? true : readStop();
+
+ fail:
+  return false;
+}
+//------------------------------------------------------------------------------
+bool DedicatedSpiCard::setDedicatedSpi(bool value) {
+  if (!syncDevice()) {
+    return false;
+  }
+  m_dedicatedSpi = value;
+  return true;
+}
+//------------------------------------------------------------------------------
+bool DedicatedSpiCard::writeSector(uint32_t sector, const uint8_t* src) {
+  if (m_dedicatedSpi) {
+    return writeSectors(sector, src, 1);
+  }
+  return SharedSpiCard::writeSector(sector, src);
+}
+//------------------------------------------------------------------------------
+bool DedicatedSpiCard::writeSectors(
+    uint32_t sector, const uint8_t* src, size_t ns) {
+  if (sdState() != WRITE_STATE || m_curSector != sector) {
+    if (!writeStart(sector)) {
+      goto fail;
+    }
+    m_curSector = sector;
+  }
+  for (size_t i = 0; i < ns; i++, src += 512) {
+    if (!writeData(src)) {
+      goto fail;
+    }
+  }
+  m_curSector += ns;
+  return m_dedicatedSpi ? true : writeStop();
+
+fail:
+  return false;
+}

+ 451 - 0
lib/SdFat_NoArduino/src/SdCard/SdSpiCard.h

@@ -0,0 +1,451 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+/**
+ * \file
+ * \brief SdSpiCard class for V2 SD/SDHC cards
+ */
+#ifndef SdSpiCard_h
+#define SdSpiCard_h
+#include <stddef.h>
+#include "../common/SysCall.h"
+#include "SdCardInfo.h"
+#include "SdCardInterface.h"
+#include "../SpiDriver/SdSpiDriver.h"
+/** Verify correct SPI active if non-zero. */
+#define CHECK_SPI_ACTIVE 0
+#if CHECK_SPI_ACTIVE
+/** Check SPI active. */
+#define SPI_ASSERT_ACTIVE {if (!m_spiActive) {\
+  Serial.print(F("SPI_ASSERTACTIVE"));\
+  Serial.println(__LINE__);}}
+#else  // CHECK_SPI_ACTIVE
+/** Do not check SPI active. */
+#define SPI_ASSERT_ACTIVE
+#endif  // CHECK_SPI_ACTIVE
+//==============================================================================
+/**
+ * \class SharedSpiCard
+ * \brief Raw access to SD and SDHC flash memory cards via shared SPI port.
+ */
+#if HAS_SDIO_CLASS
+class SharedSpiCard : public SdCardInterface {
+#elif USE_BLOCK_DEVICE_INTERFACE
+class SharedSpiCard : public FsBlockDeviceInterface {
+#else  // HAS_SDIO_CLASS
+class SharedSpiCard {
+#endif  // HAS_SDIO_CLASS
+ public:
+  /** SD is in idle state */
+  static const uint8_t IDLE_STATE = 0;
+  /** SD is in multi-sector read state. */
+  static const uint8_t READ_STATE = 1;
+  /** SD is in multi-sector write state. */
+  static const uint8_t WRITE_STATE = 2;
+  /** Construct an instance of SharedSpiCard. */
+  SharedSpiCard() {}
+  /** Initialize the SD card.
+   * \param[in] spiConfig SPI card configuration.
+   * \return true for success or false for failure.
+   */
+  bool begin(SdSpiConfig spiConfig);
+  /** CMD6 Switch mode: Check Function Set Function.
+   * \param[in] arg CMD6 argument.
+   * \param[out] status return status data.
+   *
+   * \return true for success or false for failure.
+   */
+  bool cardCMD6(uint32_t arg, uint8_t* status);
+  /** End use of card */
+  void end();
+  /** Erase a range of sectors.
+   *
+   * \param[in] firstSector The address of the first sector in the range.
+   * \param[in] lastSector The address of the last sector in the range.
+   *
+   * \note This function requests the SD card to do a flash erase for a
+   * range of sectors.  The data on the card after an erase operation is
+   * either 0 or 1, depends on the card vendor.  The card must support
+   * single sector erase.
+   *
+   * \return true for success or false for failure.
+   */
+  bool erase(uint32_t firstSector, uint32_t lastSector);
+  /** Determine if card supports single sector erase.
+   *
+   * \return true is returned if single sector erase is supported.
+   * false is returned if single sector erase is not supported.
+   */
+  bool eraseSingleSectorEnable();
+  /**
+   *  Set SD error code.
+   *  \param[in] code value for error code.
+   */
+  void error(uint8_t code) {
+//    (void)code;
+    m_errorCode = code;
+  }
+  /**
+   * \return code for the last error. See SdCardInfo.h for a list of error codes.
+   */
+  uint8_t errorCode() const {
+    return m_errorCode;
+  }
+  /** \return error data for last error. */
+  uint32_t errorData() const {
+    return m_status;
+  }
+  /** \return false for shared class. */
+  bool hasDedicatedSpi() {return false;}
+  /**
+   * Check for busy.  MISO low indicates the card is busy.
+   *
+   * \return true if busy else false.
+   */
+  bool isBusy();
+  /** \return false, can't be in dedicated state. */
+  bool isDedicatedSpi() {return false;}
+  /**
+   * Read a card's CID register. The CID contains card identification
+   * information such as Manufacturer ID, Product name, Product serial
+   * number and Manufacturing date.
+   *
+   * \param[out] cid pointer to area for returned data.
+   *
+   * \return true for success or false for failure.
+   */
+  bool readCID(cid_t* cid) {
+    return readRegister(CMD10, cid);
+  }
+  /**
+   * Read a card's CSD register. The CSD contains Card-Specific Data that
+   * provides information regarding access to the card's contents.
+   *
+   * \param[out] csd pointer to area for returned data.
+   *
+   * \return true for success or false for failure.
+   */
+  bool readCSD(csd_t* csd) {
+    return readRegister(CMD9, csd);
+  }
+  /** Read one data sector in a multiple sector read sequence
+   *
+   * \param[out] dst Pointer to the location for the data to be read.
+   *
+   * \return true for success or false for failure.
+   */
+  bool readData(uint8_t* dst);
+  /** Read OCR register.
+   *
+   * \param[out] ocr Value of OCR register.
+   * \return true for success or false for failure.
+   */
+  bool readOCR(uint32_t* ocr);
+  /** Read SCR register.
+   *
+   * \param[out] scr Value of SCR register.
+   * \return true for success or false for failure.
+   */
+  bool readSCR(scr_t* scr);
+  /**
+   * Read a 512 byte sector from an SD card.
+   *
+   * \param[in] sector Logical sector to be read.
+   * \param[out] dst Pointer to the location that will receive the data.
+   * \return true for success or false for failure.
+   */
+  bool readSector(uint32_t sector, uint8_t* dst);
+  /**
+   * Read multiple 512 byte sectors from an SD card.
+   *
+   * \param[in] sector Logical sector to be read.
+   * \param[in] ns Number of sectors to be read.
+   * \param[out] dst Pointer to the location that will receive the data.
+   * \return true for success or false for failure.
+   */
+  bool readSectors(uint32_t sector, uint8_t* dst, size_t ns);
+
+  /** Start a read multiple sector sequence.
+   *
+   * \param[in] sector Address of first sector in sequence.
+   *
+   * \note This function is used with readData() and readStop() for optimized
+   * multiple sector reads.  SPI chipSelect must be low for the entire sequence.
+   *
+   * \return true for success or false for failure.
+   */
+  bool readStart(uint32_t sector);
+  /** Return the 64 byte card status
+   * \param[out] status location for 64 status bytes.
+   * \return true for success or false for failure.
+   */
+  bool readStatus(SdStatus* status);
+  /** End a read multiple sectors sequence.
+   *
+   * \return true for success or false for failure.
+   */
+  bool readStop();
+  /** \return SD multi-sector read/write state */
+  uint8_t sdState() {return m_state;}
+  /**
+   * Determine the size of an SD flash memory card.
+   *
+   * \return The number of 512 byte data sectors in the card
+   *         or zero if an error occurs.
+   */
+  uint32_t sectorCount();
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+  // Use sectorCount(). cardSize() will be removed in the future.
+  uint32_t __attribute__((error("use sectorCount()"))) cardSize();
+#endif  // DOXYGEN_SHOULD_SKIP_THIS
+  /** Set SPI sharing state
+   * \param[in] value desired state.
+   * \return false for shared card
+   */
+  bool setDedicatedSpi(bool value) {
+    (void)value;
+    return false;
+  }
+  /** end a mult-sector transfer.
+   *
+   * \return true for success or false for failure.
+   */
+  bool stopTransfer();
+  /** \return success if sync successful. Not for user apps. */
+  bool syncDevice();
+  /** Return the card type: SD V1, SD V2 or SDHC/SDXC
+   * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC/SDXC.
+   */
+  uint8_t type() const {
+    return m_type;
+  }
+  /**
+   * Write a 512 byte sector to an SD card.
+   *
+   * \param[in] sector Logical sector to be written.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \return true for success or false for failure.
+   */
+  bool writeSector(uint32_t sector, const uint8_t* src);
+  /**
+   * Write multiple 512 byte sectors to an SD card.
+   *
+   * \param[in] sector Logical sector to be written.
+   * \param[in] ns Number of sectors to be written.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \return true for success or false for failure.
+   */
+  bool writeSectors(uint32_t sector, const uint8_t* src, size_t ns);
+  /** Write one data sector in a multiple sector write sequence.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \return true for success or false for failure.
+   */
+  bool writeData(const uint8_t* src);
+  /** Start a write multiple sectors sequence.
+   *
+   * \param[in] sector Address of first sector in sequence.
+   *
+   * \note This function is used with writeData() and writeStop()
+   * for optimized multiple sector writes.
+   *
+   * \return true for success or false for failure.
+   */
+  bool writeStart(uint32_t sector);
+
+  /** End a write multiple sectors sequence.
+   *
+   * \return true for success or false for failure.
+   */
+  bool writeStop();
+
+ private:
+  // private functions
+  uint8_t cardAcmd(uint8_t cmd, uint32_t arg) {
+    cardCommand(CMD55, 0);
+    return cardCommand(cmd, arg);
+  }
+  uint8_t cardCommand(uint8_t cmd, uint32_t arg);
+  bool readData(uint8_t* dst, size_t count);
+  bool readRegister(uint8_t cmd, void* buf);
+  void spiSelect() {
+    sdCsWrite(m_csPin, false);
+  }
+  void spiStart();
+  void spiStop();
+  void spiUnselect() {
+    sdCsWrite(m_csPin, true);
+  }
+  void type(uint8_t value) {
+    m_type = value;
+  }
+  bool waitReady(uint16_t ms);
+  bool writeData(uint8_t token, const uint8_t* src);
+#if SPI_DRIVER_SELECT < 2
+  void spiActivate() {
+    m_spiDriver.activate();
+  }
+  void spiBegin(SdSpiConfig spiConfig) {
+    m_spiDriver.begin(spiConfig);
+  }
+  void spiDeactivate() {
+    m_spiDriver.deactivate();
+  }
+  void spiEnd() {
+    m_spiDriver.end();
+  }
+  uint8_t spiReceive() {
+    SPI_ASSERT_ACTIVE;
+    return m_spiDriver.receive();
+  }
+  uint8_t spiReceive(uint8_t* buf, size_t n) {
+    SPI_ASSERT_ACTIVE;
+    return m_spiDriver.receive(buf, n);
+  }
+  void spiSend(uint8_t data) {
+    SPI_ASSERT_ACTIVE;
+    m_spiDriver.send(data);
+  }
+  void spiSend(const uint8_t* buf, size_t n) {
+    SPI_ASSERT_ACTIVE;
+    m_spiDriver.send(buf, n);
+  }
+  void spiSetSckSpeed(uint32_t maxSck) {
+    m_spiDriver.setSckSpeed(maxSck);
+  }
+  SdSpiDriver m_spiDriver;
+#else  // SPI_DRIVER_SELECT < 2
+  void spiActivate() {
+    m_spiDriverPtr->activate();
+  }
+  void spiBegin(SdSpiConfig spiConfig) {
+    m_spiDriverPtr->begin(spiConfig);
+  }
+  void spiDeactivate() {
+    m_spiDriverPtr->deactivate();
+  }
+  void spiEnd() {
+    m_spiDriverPtr->end();
+  }
+  uint8_t spiReceive() {
+    SPI_ASSERT_ACTIVE;
+    return m_spiDriverPtr->receive();
+  }
+  uint8_t spiReceive(uint8_t* buf, size_t n) {
+    SPI_ASSERT_ACTIVE;
+    return m_spiDriverPtr->receive(buf, n);
+  }
+  void spiSend(uint8_t data) {
+    SPI_ASSERT_ACTIVE;
+    m_spiDriverPtr->send(data);
+  }
+  void spiSend(const uint8_t* buf, size_t n) {
+    SPI_ASSERT_ACTIVE;
+    m_spiDriverPtr->send(buf, n);
+  }
+  void spiSetSckSpeed(uint32_t maxSck) {
+    m_spiDriverPtr->setSckSpeed(maxSck);
+  }
+  SdSpiDriver* m_spiDriverPtr;
+
+#endif  // SPI_DRIVER_SELECT < 2
+  bool m_beginCalled = false;
+  SdCsPin_t m_csPin;
+  uint8_t m_errorCode = SD_CARD_ERROR_INIT_NOT_CALLED;
+  bool    m_spiActive;
+  uint8_t m_state;
+  uint8_t m_status;
+  uint8_t m_type = 0;
+};
+
+//==============================================================================
+/**
+ * \class DedicatedSpiCard
+ * \brief Raw access to SD and SDHC flash memory cards via dedicate SPI port.
+ */
+class DedicatedSpiCard : public SharedSpiCard {
+ public:
+  /** Construct an instance of DedicatedSpiCard. */
+  DedicatedSpiCard() {}
+  /** Initialize the SD card.
+   * \param[in] spiConfig SPI card configuration.
+   * \return true for success or false for failure.
+   */
+  bool begin(SdSpiConfig spiConfig);
+  /** \return true, can be in dedicaded state. */
+  bool hasDedicatedSpi() {return true;}
+  /** \return true if in dedicated SPI state. */
+  bool isDedicatedSpi() {return m_dedicatedSpi;}
+  /**
+   * Read a 512 byte sector from an SD card.
+   *
+   * \param[in] sector Logical sector to be read.
+   * \param[out] dst Pointer to the location that will receive the data.
+   * \return true for success or false for failure.
+   */
+  bool readSector(uint32_t sector, uint8_t* dst);
+  /**
+   * Read multiple 512 byte sectors from an SD card.
+   *
+   * \param[in] sector Logical sector to be read.
+   * \param[in] ns Number of sectors to be read.
+   * \param[out] dst Pointer to the location that will receive the data.
+   * \return true for success or false for failure.
+   */
+  bool readSectors(uint32_t sector, uint8_t* dst, size_t ns);
+  /** Set SPI sharing state
+   * \param[in] value desired state.
+   * \return true for success else false;
+   */
+  bool setDedicatedSpi(bool value);
+  /**
+   * Write a 512 byte sector to an SD card.
+   *
+   * \param[in] sector Logical sector to be written.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \return true for success or false for failure.
+   */
+  bool writeSector(uint32_t sector, const uint8_t* src);
+  /**
+   * Write multiple 512 byte sectors to an SD card.
+   *
+   * \param[in] sector Logical sector to be written.
+   * \param[in] ns Number of sectors to be written.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \return true for success or false for failure.
+   */
+  bool writeSectors(uint32_t sector, const uint8_t* src, size_t ns);
+
+ private:
+  uint32_t m_curSector;
+  bool m_dedicatedSpi = false;
+};
+//==============================================================================
+#if ENABLE_DEDICATED_SPI
+/** typedef for dedicated SPI. */
+typedef DedicatedSpiCard SdSpiCard;
+#else
+/** typedef for shared SPI. */
+typedef SharedSpiCard SdSpiCard;
+#endif
+#endif  // SdSpiCard_h

+ 267 - 0
lib/SdFat_NoArduino/src/SdCard/SdioCard.h

@@ -0,0 +1,267 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef SdioCard_h
+#define SdioCard_h
+#include "../common/SysCall.h"
+#include "SdCardInterface.h"
+
+#define FIFO_SDIO 0
+#define DMA_SDIO 1
+/**
+ * \class SdioConfig
+ * \brief SDIO card configuration.
+ */
+class SdioConfig {
+ public:
+  SdioConfig() {}
+  /**
+   * SdioConfig constructor.
+   * \param[in] opt SDIO options.
+   */
+  explicit SdioConfig(uint8_t opt) : m_options(opt) {}
+  /** \return SDIO card options. */
+  uint8_t options() {return m_options;}
+  /** \return true if DMA_SDIO. */
+  bool useDma() {return m_options & DMA_SDIO;}
+ private:
+  uint8_t m_options = FIFO_SDIO;
+};
+//------------------------------------------------------------------------------
+/**
+ * \class SdioCard
+ * \brief Raw SDIO access to SD and SDHC flash memory cards.
+ */
+class SdioCard : public SdCardInterface {
+ public:
+  /** Initialize the SD card.
+   * \param[in] sdioConfig SDIO card configuration.
+   * \return true for success or false for failure.
+   */
+  bool begin(SdioConfig sdioConfig);
+  /** CMD6 Switch mode: Check Function Set Function.
+   * \param[in] arg CMD6 argument.
+   * \param[out] status return status data.
+   *
+   * \return true for success or false for failure.
+   */
+  bool cardCMD6(uint32_t arg, uint8_t* status);
+  /** Disable an SDIO card.
+   * not implemented.
+   */
+  void end() {}
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+    uint32_t __attribute__((error("use sectorCount()"))) cardSize();
+#endif  // DOXYGEN_SHOULD_SKIP_THIS
+  /** Erase a range of sectors.
+   *
+   * \param[in] firstSector The address of the first sector in the range.
+   * \param[in] lastSector The address of the last sector in the range.
+   *
+   * \note This function requests the SD card to do a flash erase for a
+   * range of sectors.  The data on the card after an erase operation is
+   * either 0 or 1, depends on the card vendor.  The card must support
+   * single sector erase.
+   *
+   * \return true for success or false for failure.
+   */
+  bool erase(uint32_t firstSector, uint32_t lastSector);
+  /**
+   * \return code for the last error. See SdCardInfo.h for a list of error codes.
+   */
+  uint8_t errorCode() const;
+  /** \return error data for last error. */
+  uint32_t errorData() const;
+  /** \return error line for last error. Tmp function for debug. */
+  uint32_t errorLine() const;
+  /**
+   * Check for busy with CMD13.
+   *
+   * \return true if busy else false.
+   */
+  bool isBusy();
+  /** \return the SD clock frequency in kHz. */
+  uint32_t kHzSdClk();
+  /**
+   * Read a 512 byte sector from an SD card.
+   *
+   * \param[in] sector Logical sector to be read.
+   * \param[out] dst Pointer to the location that will receive the data.
+   * \return true for success or false for failure.
+   */
+  bool readSector(uint32_t sector, uint8_t* dst);
+  /**
+   * Read multiple 512 byte sectors from an SD card.
+   *
+   * \param[in] sector Logical sector to be read.
+   * \param[in] ns Number of sectors to be read.
+   * \param[out] dst Pointer to the location that will receive the data.
+   * \return true for success or false for failure.
+   */
+  bool readSectors(uint32_t sector, uint8_t* dst, size_t ns);
+  /**
+   * Read a card's CID register. The CID contains card identification
+   * information such as Manufacturer ID, Product name, Product serial
+   * number and Manufacturing date.
+   *
+   * \param[out] cid pointer to area for returned data.
+   *
+   * \return true for success or false for failure.
+   */
+  bool readCID(cid_t* cid);
+  /**
+   * Read a card's CSD register. The CSD contains Card-Specific Data that
+   * provides information regarding access to the card's contents.
+   *
+   * \param[out] csd pointer to area for returned data.
+   *
+   * \return true for success or false for failure.
+   */
+  bool readCSD(csd_t* csd);
+  /** Read one data sector in a multiple sector read sequence
+   *
+   * \param[out] dst Pointer to the location for the data to be read.
+   *
+   * \return true for success or false for failure.
+   */
+  bool readData(uint8_t* dst);
+  /** Read OCR register.
+   *
+   * \param[out] ocr Value of OCR register.
+   * \return true for success or false for failure.
+   */
+  bool readOCR(uint32_t* ocr);
+  /** Read SCR register.
+   *
+   * \param[out] scr Value of SCR register.
+   * \return true for success or false for failure.
+   */
+  bool readSCR(scr_t *scr);
+  /** Start a read multiple sectors sequence.
+   *
+   * \param[in] sector Address of first sector in sequence.
+   *
+   * \note This function is used with readData() and readStop() for optimized
+   * multiple sector reads.  SPI chipSelect must be low for the entire sequence.
+   *
+   * \return true for success or false for failure.
+   */
+  bool readStart(uint32_t sector);
+  /** Start a read multiple sectors sequence.
+   *
+   * \param[in] sector Address of first sector in sequence.
+   * \param[in] count Maximum sector count.
+   * \note This function is used with readData() and readStop() for optimized
+   * multiple sector reads.  SPI chipSelect must be low for the entire sequence.
+   *
+   * \return true for success or false for failure.
+   */
+  bool readStart(uint32_t sector, uint32_t count);
+  /** End a read multiple sectors sequence.
+   *
+   * \return true for success or false for failure.
+   */
+  bool readStop();
+  /** \return SDIO card status. */
+  uint32_t status();
+    /**
+   * Determine the size of an SD flash memory card.
+   *
+   * \return The number of 512 byte data sectors in the card
+   *         or zero if an error occurs.
+   */
+  uint32_t sectorCount();
+  /**
+   *  Send CMD12 to stop read or write.
+   *
+   * \param[in] blocking If true, wait for command complete.
+   *
+   * \return true for success or false for failure.
+   */
+  bool stopTransmission(bool blocking);
+  /** \return success if sync successful. Not for user apps. */
+  bool syncDevice();
+  /** Return the card type: SD V1, SD V2 or SDHC
+   * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC.
+   */
+  uint8_t type() const;
+  /**
+   * Writes a 512 byte sector to an SD card.
+   *
+   * \param[in] sector Logical sector to be written.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \return true for success or false for failure.
+   */
+  bool writeSector(uint32_t sector, const uint8_t* src);
+  /**
+   * Write multiple 512 byte sectors to an SD card.
+   *
+   * \param[in] sector Logical sector to be written.
+   * \param[in] ns Number of sectors to be written.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \return true for success or false for failure.
+   */
+  bool writeSectors(uint32_t sector, const uint8_t* src, size_t ns);
+  /** Write one data sector in a multiple sector write sequence.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \return true for success or false for failure.
+   */
+  bool writeData(const uint8_t* src);
+  /** Start a write multiple sectors sequence.
+   *
+   * \param[in] sector Address of first sector in sequence.
+   *
+   * \note This function is used with writeData() and writeStop()
+   * for optimized multiple sector writes.
+   *
+   * \return true for success or false for failure.
+   */
+  bool writeStart(uint32_t sector);
+  /** Start a write multiple sectors sequence.
+   *
+   * \param[in] sector Address of first sector in sequence.
+   * \param[in] count Maximum sector count.
+   * \note This function is used with writeData() and writeStop()
+   * for optimized multiple sector writes.
+   *
+   * \return true for success or false for failure.
+   */
+  bool writeStart(uint32_t sector, uint32_t count);
+
+  /** End a write multiple sectors sequence.
+   *
+   * \return true for success or false for failure.
+   */
+  bool writeStop();
+
+ private:
+  static const uint8_t IDLE_STATE = 0;
+  static const uint8_t READ_STATE = 1;
+  static const uint8_t WRITE_STATE = 2;
+  uint32_t m_curSector;
+  SdioConfig m_sdioConfig;
+  uint8_t m_curState = IDLE_STATE;
+};
+#endif  // SdioCard_h

+ 1120 - 0
lib/SdFat_NoArduino/src/SdCard/SdioTeensy.cpp

@@ -0,0 +1,1120 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#if defined(__MK64FX512__) || defined(__MK66FX1M0__) || defined(__IMXRT1062__)
+#include "SdioTeensy.h"
+#include "SdCardInfo.h"
+#include "SdioCard.h"
+//==============================================================================
+// limit of K66 due to errata KINETIS_K_0N65N.
+const uint32_t MAX_BLKCNT = 0XFFFF;
+//==============================================================================
+#define SDHC_PROCTL_DTW_4BIT 0x01
+const uint32_t FIFO_WML = 16;
+const uint32_t CMD8_RETRIES = 3;
+const uint32_t BUSY_TIMEOUT_MICROS = 1000000;
+//==============================================================================
+const uint32_t SDHC_IRQSTATEN_MASK =
+               SDHC_IRQSTATEN_DMAESEN | SDHC_IRQSTATEN_AC12ESEN |
+               SDHC_IRQSTATEN_DEBESEN | SDHC_IRQSTATEN_DCESEN |
+               SDHC_IRQSTATEN_DTOESEN | SDHC_IRQSTATEN_CIESEN |
+               SDHC_IRQSTATEN_CEBESEN | SDHC_IRQSTATEN_CCESEN |
+               SDHC_IRQSTATEN_CTOESEN | SDHC_IRQSTATEN_DINTSEN |
+               SDHC_IRQSTATEN_TCSEN | SDHC_IRQSTATEN_CCSEN;
+
+const uint32_t SDHC_IRQSTAT_CMD_ERROR =
+               SDHC_IRQSTAT_CIE | SDHC_IRQSTAT_CEBE |
+               SDHC_IRQSTAT_CCE | SDHC_IRQSTAT_CTOE;
+
+const uint32_t SDHC_IRQSTAT_DATA_ERROR =
+               SDHC_IRQSTAT_AC12E | SDHC_IRQSTAT_DEBE |
+               SDHC_IRQSTAT_DCE | SDHC_IRQSTAT_DTOE;
+
+const uint32_t SDHC_IRQSTAT_ERROR =
+               SDHC_IRQSTAT_DMAE | SDHC_IRQSTAT_CMD_ERROR |
+               SDHC_IRQSTAT_DATA_ERROR;
+
+const uint32_t SDHC_IRQSIGEN_MASK =
+               SDHC_IRQSIGEN_DMAEIEN | SDHC_IRQSIGEN_AC12EIEN |
+               SDHC_IRQSIGEN_DEBEIEN | SDHC_IRQSIGEN_DCEIEN |
+               SDHC_IRQSIGEN_DTOEIEN | SDHC_IRQSIGEN_CIEIEN |
+               SDHC_IRQSIGEN_CEBEIEN | SDHC_IRQSIGEN_CCEIEN |
+               SDHC_IRQSIGEN_CTOEIEN | SDHC_IRQSIGEN_TCIEN;
+//==============================================================================
+const uint32_t CMD_RESP_NONE = SDHC_XFERTYP_RSPTYP(0);
+
+const uint32_t CMD_RESP_R1 = SDHC_XFERTYP_CICEN | SDHC_XFERTYP_CCCEN |
+                             SDHC_XFERTYP_RSPTYP(2);
+
+const uint32_t CMD_RESP_R1b = SDHC_XFERTYP_CICEN | SDHC_XFERTYP_CCCEN |
+                              SDHC_XFERTYP_RSPTYP(3);
+
+const uint32_t CMD_RESP_R2 = SDHC_XFERTYP_CCCEN | SDHC_XFERTYP_RSPTYP(1);
+
+const uint32_t CMD_RESP_R3 = SDHC_XFERTYP_RSPTYP(2);
+
+const uint32_t CMD_RESP_R6 = CMD_RESP_R1;
+
+const uint32_t CMD_RESP_R7 = CMD_RESP_R1;
+
+#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
+const uint32_t DATA_READ = SDHC_XFERTYP_DTDSEL | SDHC_XFERTYP_DPSEL;
+
+const uint32_t DATA_READ_DMA = DATA_READ | SDHC_XFERTYP_DMAEN;
+
+const uint32_t DATA_READ_MULTI_DMA = DATA_READ_DMA | SDHC_XFERTYP_MSBSEL |
+                                     SDHC_XFERTYP_AC12EN | SDHC_XFERTYP_BCEN;
+
+const uint32_t DATA_READ_MULTI_PGM = DATA_READ | SDHC_XFERTYP_MSBSEL |
+                                     SDHC_XFERTYP_BCEN;
+
+const uint32_t DATA_WRITE_DMA = SDHC_XFERTYP_DPSEL | SDHC_XFERTYP_DMAEN;
+
+const uint32_t DATA_WRITE_MULTI_DMA = DATA_WRITE_DMA | SDHC_XFERTYP_MSBSEL |
+                                      SDHC_XFERTYP_AC12EN | SDHC_XFERTYP_BCEN;
+
+const uint32_t DATA_WRITE_MULTI_PGM = SDHC_XFERTYP_DPSEL | SDHC_XFERTYP_MSBSEL |
+                                      SDHC_XFERTYP_BCEN;
+
+#elif defined(__IMXRT1062__)
+// Use low bits for SDHC_MIX_CTRL since bits 15-0 of SDHC_XFERTYP are reserved.
+const uint32_t SDHC_MIX_CTRL_MASK = SDHC_MIX_CTRL_DMAEN | SDHC_MIX_CTRL_BCEN |
+                                    SDHC_MIX_CTRL_AC12EN |
+                                    SDHC_MIX_CTRL_DDR_EN |
+                                    SDHC_MIX_CTRL_DTDSEL |
+                                    SDHC_MIX_CTRL_MSBSEL |
+                                    SDHC_MIX_CTRL_NIBBLE_POS |
+                                    SDHC_MIX_CTRL_AC23EN;
+
+const uint32_t DATA_READ = SDHC_MIX_CTRL_DTDSEL | SDHC_XFERTYP_DPSEL;
+
+const uint32_t DATA_READ_DMA = DATA_READ | SDHC_MIX_CTRL_DMAEN;
+
+const uint32_t DATA_READ_MULTI_DMA = DATA_READ_DMA | SDHC_MIX_CTRL_MSBSEL |
+                                     SDHC_MIX_CTRL_AC12EN | SDHC_MIX_CTRL_BCEN;
+
+const uint32_t DATA_READ_MULTI_PGM = DATA_READ | SDHC_MIX_CTRL_MSBSEL;
+
+
+const uint32_t DATA_WRITE_DMA = SDHC_XFERTYP_DPSEL | SDHC_MIX_CTRL_DMAEN;
+
+const uint32_t DATA_WRITE_MULTI_DMA = DATA_WRITE_DMA | SDHC_MIX_CTRL_MSBSEL |
+                                      SDHC_MIX_CTRL_AC12EN | SDHC_MIX_CTRL_BCEN;
+
+const uint32_t DATA_WRITE_MULTI_PGM = SDHC_XFERTYP_DPSEL | SDHC_MIX_CTRL_MSBSEL;
+
+#endif  // defined(__MK64FX512__) || defined(__MK66FX1M0__)
+
+const uint32_t ACMD6_XFERTYP = SDHC_XFERTYP_CMDINX(ACMD6) | CMD_RESP_R1;
+
+const uint32_t ACMD41_XFERTYP = SDHC_XFERTYP_CMDINX(ACMD41) | CMD_RESP_R3;
+
+const uint32_t ACMD51_XFERTYP = SDHC_XFERTYP_CMDINX(ACMD51) | CMD_RESP_R1 |
+                                DATA_READ_DMA;
+
+const uint32_t CMD0_XFERTYP = SDHC_XFERTYP_CMDINX(CMD0) | CMD_RESP_NONE;
+
+const uint32_t CMD2_XFERTYP = SDHC_XFERTYP_CMDINX(CMD2) | CMD_RESP_R2;
+
+const uint32_t CMD3_XFERTYP = SDHC_XFERTYP_CMDINX(CMD3) | CMD_RESP_R6;
+
+const uint32_t CMD6_XFERTYP = SDHC_XFERTYP_CMDINX(CMD6) | CMD_RESP_R1 |
+                              DATA_READ_DMA;
+
+const uint32_t CMD7_XFERTYP = SDHC_XFERTYP_CMDINX(CMD7) | CMD_RESP_R1b;
+
+const uint32_t CMD8_XFERTYP = SDHC_XFERTYP_CMDINX(CMD8) | CMD_RESP_R7;
+
+const uint32_t CMD9_XFERTYP = SDHC_XFERTYP_CMDINX(CMD9) | CMD_RESP_R2;
+
+const uint32_t CMD10_XFERTYP = SDHC_XFERTYP_CMDINX(CMD10) | CMD_RESP_R2;
+
+const uint32_t CMD11_XFERTYP = SDHC_XFERTYP_CMDINX(CMD11) | CMD_RESP_R1;
+
+const uint32_t CMD12_XFERTYP = SDHC_XFERTYP_CMDINX(CMD12) | CMD_RESP_R1b |
+                               SDHC_XFERTYP_CMDTYP(3);
+
+const uint32_t CMD13_XFERTYP = SDHC_XFERTYP_CMDINX(CMD13) | CMD_RESP_R1;
+
+const uint32_t CMD17_DMA_XFERTYP = SDHC_XFERTYP_CMDINX(CMD17) | CMD_RESP_R1 |
+                                   DATA_READ_DMA;
+
+const uint32_t CMD18_DMA_XFERTYP = SDHC_XFERTYP_CMDINX(CMD18) | CMD_RESP_R1 |
+                                   DATA_READ_MULTI_DMA;
+
+const uint32_t CMD18_PGM_XFERTYP = SDHC_XFERTYP_CMDINX(CMD18) | CMD_RESP_R1 |
+                                   DATA_READ_MULTI_PGM;
+
+const uint32_t CMD24_DMA_XFERTYP = SDHC_XFERTYP_CMDINX(CMD24) | CMD_RESP_R1 |
+                                   DATA_WRITE_DMA;
+
+const uint32_t CMD25_DMA_XFERTYP = SDHC_XFERTYP_CMDINX(CMD25) | CMD_RESP_R1 |
+                                   DATA_WRITE_MULTI_DMA;
+
+const uint32_t CMD25_PGM_XFERTYP = SDHC_XFERTYP_CMDINX(CMD25) | CMD_RESP_R1 |
+                                   DATA_WRITE_MULTI_PGM;
+
+const uint32_t CMD32_XFERTYP = SDHC_XFERTYP_CMDINX(CMD32) | CMD_RESP_R1;
+
+const uint32_t CMD33_XFERTYP = SDHC_XFERTYP_CMDINX(CMD33) | CMD_RESP_R1;
+
+const uint32_t CMD38_XFERTYP = SDHC_XFERTYP_CMDINX(CMD38) | CMD_RESP_R1b;
+
+const uint32_t CMD55_XFERTYP = SDHC_XFERTYP_CMDINX(CMD55) | CMD_RESP_R1;
+
+//==============================================================================
+static bool cardCommand(uint32_t xfertyp, uint32_t arg);
+static void enableGPIO(bool enable);
+static void enableDmaIrs();
+static void initSDHC();
+static bool isBusyCMD13();
+static bool isBusyCommandComplete();
+static bool isBusyCommandInhibit();
+static bool readReg16(uint32_t xfertyp, void* data);
+static void setSdclk(uint32_t kHzMax);
+static bool yieldTimeout(bool (*fcn)());
+static bool waitDmaStatus();
+static bool waitTimeout(bool (*fcn)());
+//------------------------------------------------------------------------------
+static bool (*m_busyFcn)() = 0;
+static bool m_initDone = false;
+static bool m_version2;
+static bool m_highCapacity;
+static bool m_transferActive = false;
+static uint8_t m_errorCode = SD_CARD_ERROR_INIT_NOT_CALLED;
+static uint32_t m_errorLine = 0;
+static uint32_t m_rca;
+static volatile bool m_dmaBusy = false;
+static volatile uint32_t m_irqstat;
+static uint32_t m_sdClkKhz = 0;
+static uint32_t m_ocr;
+static cid_t m_cid;
+static csd_t m_csd;
+static scr_t m_scr;
+//==============================================================================
+#define DBG_TRACE Serial.print("TRACE."); Serial.println(__LINE__); delay(200);
+#define USE_DEBUG_MODE 0
+#if USE_DEBUG_MODE
+#define DBG_IRQSTAT() if (SDHC_IRQSTAT) {Serial.print(__LINE__);\
+        Serial.print(" IRQSTAT "); Serial.println(SDHC_IRQSTAT, HEX);}
+static void printRegs(uint32_t line) {
+  uint32_t blkattr = SDHC_BLKATTR;
+  uint32_t xfertyp = SDHC_XFERTYP;
+  uint32_t prsstat = SDHC_PRSSTAT;
+  uint32_t proctl = SDHC_PROCTL;
+  uint32_t irqstat = SDHC_IRQSTAT;
+  Serial.print("\nLINE: ");
+  Serial.println(line);
+  Serial.print("BLKATTR ");
+  Serial.println(blkattr, HEX);
+  Serial.print("XFERTYP ");
+  Serial.print(xfertyp, HEX);
+  Serial.print(" CMD");
+  Serial.print(xfertyp >> 24);
+  Serial.print(" TYP");
+  Serial.print((xfertyp >> 2) & 3);
+  if (xfertyp & SDHC_XFERTYP_DPSEL) {Serial.print(" DPSEL");}
+  Serial.println();
+  Serial.print("PRSSTAT ");
+  Serial.print(prsstat, HEX);
+  if (prsstat & SDHC_PRSSTAT_BREN) {Serial.print(" BREN");}
+  if (prsstat & SDHC_PRSSTAT_BWEN) {Serial.print(" BWEN");}
+  if (prsstat & SDHC_PRSSTAT_RTA) {Serial.print(" RTA");}
+  if (prsstat & SDHC_PRSSTAT_WTA) {Serial.print(" WTA");}
+  if (prsstat & SDHC_PRSSTAT_SDOFF) {Serial.print(" SDOFF");}
+  if (prsstat & SDHC_PRSSTAT_PEROFF) {Serial.print(" PEROFF");}
+  if (prsstat & SDHC_PRSSTAT_HCKOFF) {Serial.print(" HCKOFF");}
+  if (prsstat & SDHC_PRSSTAT_IPGOFF) {Serial.print(" IPGOFF");}
+  if (prsstat & SDHC_PRSSTAT_SDSTB) {Serial.print(" SDSTB");}
+  if (prsstat & SDHC_PRSSTAT_DLA) {Serial.print(" DLA");}
+  if (prsstat & SDHC_PRSSTAT_CDIHB) {Serial.print(" CDIHB");}
+  if (prsstat & SDHC_PRSSTAT_CIHB) {Serial.print(" CIHB");}
+  Serial.println();
+  Serial.print("PROCTL ");
+  Serial.print(proctl, HEX);
+  if (proctl & SDHC_PROCTL_SABGREQ) Serial.print(" SABGREQ");
+  Serial.print(" EMODE");
+  Serial.print((proctl >>4) & 3);
+  Serial.print(" DWT");
+  Serial.print((proctl >>1) & 3);
+  Serial.println();
+  Serial.print("IRQSTAT ");
+  Serial.print(irqstat, HEX);
+  if (irqstat & SDHC_IRQSTAT_BGE) {Serial.print(" BGE");}
+  if (irqstat & SDHC_IRQSTAT_TC) {Serial.print(" TC");}
+  if (irqstat & SDHC_IRQSTAT_CC) {Serial.print(" CC");}
+  Serial.print("\nm_irqstat ");
+  Serial.println(m_irqstat, HEX);
+}
+#else  // USE_DEBUG_MODE
+#define DBG_IRQSTAT()
+#endif  // USE_DEBUG_MODE
+//==============================================================================
+// Error function and macro.
+#define sdError(code) setSdErrorCode(code, __LINE__)
+inline bool setSdErrorCode(uint8_t code, uint32_t line) {
+  m_errorCode = code;
+  m_errorLine = line;
+#if USE_DEBUG_MODE
+  printRegs(line);
+#endif  // USE_DEBUG_MODE
+  return false;
+}
+//==============================================================================
+// ISR
+static void sdIrs() {
+  SDHC_IRQSIGEN = 0;
+  m_irqstat = SDHC_IRQSTAT;
+  SDHC_IRQSTAT = m_irqstat;
+#if defined(__IMXRT1062__)
+  SDHC_MIX_CTRL &= ~(SDHC_MIX_CTRL_AC23EN | SDHC_MIX_CTRL_DMAEN);
+#endif
+  m_dmaBusy = false;
+}
+//==============================================================================
+// GPIO and clock functions.
+#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
+//------------------------------------------------------------------------------
+static void enableGPIO(bool enable) {
+  const uint32_t PORT_CLK = PORT_PCR_MUX(4) | PORT_PCR_DSE;
+  const uint32_t PORT_CMD_DATA = PORT_CLK   | PORT_PCR_PE | PORT_PCR_PS;
+  const uint32_t PORT_PUP = PORT_PCR_MUX(1) | PORT_PCR_PE | PORT_PCR_PS;
+
+  PORTE_PCR0 = enable ? PORT_CMD_DATA : PORT_PUP;  // SDHC_D1
+  PORTE_PCR1 = enable ? PORT_CMD_DATA : PORT_PUP;  // SDHC_D0
+  PORTE_PCR2 = enable ? PORT_CLK      : PORT_PUP;  // SDHC_CLK
+  PORTE_PCR3 = enable ? PORT_CMD_DATA : PORT_PUP;  // SDHC_CMD
+  PORTE_PCR4 = enable ? PORT_CMD_DATA : PORT_PUP;  // SDHC_D3
+  PORTE_PCR5 = enable ? PORT_CMD_DATA : PORT_PUP;  // SDHC_D2
+}
+//------------------------------------------------------------------------------
+static void initClock() {
+#ifdef HAS_KINETIS_MPU
+  // Allow SDHC Bus Master access.
+  MPU_RGDAAC0 |= 0x0C000000;
+#endif  // HAS_KINETIS_MPU
+  // Enable SDHC clock.
+  SIM_SCGC3 |= SIM_SCGC3_SDHC;
+}
+static uint32_t baseClock() { return F_CPU;}
+
+#elif defined(__IMXRT1062__)
+//------------------------------------------------------------------------------
+static void gpioMux(uint8_t mode) {
+  IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_04 = mode;  // DAT2
+  IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_05 = mode;  // DAT3
+  IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_00 = mode;  // CMD
+  IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_01 = mode;  // CLK
+  IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_02 = mode;  // DAT0
+  IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_03 = mode;  // DAT1
+}
+//------------------------------------------------------------------------------
+// add speed strength args?
+static void enableGPIO(bool enable) {
+  const uint32_t CLOCK_MASK = IOMUXC_SW_PAD_CTL_PAD_PKE |
+#if defined(ARDUINO_TEENSY41)
+                              IOMUXC_SW_PAD_CTL_PAD_DSE(7) |
+#else  // defined(ARDUINO_TEENSY41)
+                              IOMUXC_SW_PAD_CTL_PAD_DSE(4) |  ///// WHG
+#endif  // defined(ARDUINO_TEENSY41)
+                              IOMUXC_SW_PAD_CTL_PAD_SPEED(2);
+
+  const uint32_t DATA_MASK = CLOCK_MASK | IOMUXC_SW_PAD_CTL_PAD_PUE |
+                             IOMUXC_SW_PAD_CTL_PAD_PUS(1);
+  if (enable) {
+    gpioMux(0);
+    IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_04 = DATA_MASK;   // DAT2
+    IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_05 = DATA_MASK;   // DAT3
+    IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_00 = DATA_MASK;   // CMD
+    IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_01 = CLOCK_MASK;  // CLK
+    IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_02 = DATA_MASK;   // DAT0
+    IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_03 = DATA_MASK;   // DAT1
+  } else {
+    gpioMux(5);
+  }
+}
+//------------------------------------------------------------------------------
+static void initClock() {
+  /* set PDF_528 PLL2PFD0 */
+  CCM_ANALOG_PFD_528 |= (1 << 7);
+  CCM_ANALOG_PFD_528 &= ~(0x3F << 0);
+  CCM_ANALOG_PFD_528 |= ((24) & 0x3F << 0);  // 12 - 35
+  CCM_ANALOG_PFD_528 &= ~(1 << 7);
+
+  /* Enable USDHC clock. */
+  CCM_CCGR6 |= CCM_CCGR6_USDHC1(CCM_CCGR_ON);
+  CCM_CSCDR1 &= ~(CCM_CSCDR1_USDHC1_CLK_PODF_MASK);
+  CCM_CSCMR1 |= CCM_CSCMR1_USDHC1_CLK_SEL;          // PLL2PFD0
+//  CCM_CSCDR1 |= CCM_CSCDR1_USDHC1_CLK_PODF((7)); / &0x7  WHG
+  CCM_CSCDR1 |= CCM_CSCDR1_USDHC1_CLK_PODF((1));
+}
+//------------------------------------------------------------------------------
+static uint32_t baseClock() {
+  uint32_t divider = ((CCM_CSCDR1 >> 11) & 0x7) + 1;
+  return (528000000U * 3)/((CCM_ANALOG_PFD_528 & 0x3F)/6)/divider;
+}
+#endif  // defined(__MK64FX512__) || defined(__MK66FX1M0__)
+//==============================================================================
+// Static functions.
+static bool cardAcmd(uint32_t rca, uint32_t xfertyp, uint32_t arg) {
+  return cardCommand(CMD55_XFERTYP, rca) && cardCommand (xfertyp, arg);
+}
+//------------------------------------------------------------------------------
+static bool cardCommand(uint32_t xfertyp, uint32_t arg) {
+  DBG_IRQSTAT();
+  if (waitTimeout(isBusyCommandInhibit)) {
+    return false;  // Caller will set errorCode.
+  }
+  SDHC_CMDARG = arg;
+#if defined(__IMXRT1062__)
+  // Set MIX_CTRL if data transfer.
+  if (xfertyp & SDHC_XFERTYP_DPSEL) {
+    SDHC_MIX_CTRL &= ~SDHC_MIX_CTRL_MASK;
+    SDHC_MIX_CTRL |= xfertyp & SDHC_MIX_CTRL_MASK;
+  }
+  xfertyp &= ~SDHC_MIX_CTRL_MASK;
+#endif  // defined(__IMXRT1062__)
+  SDHC_XFERTYP = xfertyp;
+  if (waitTimeout(isBusyCommandComplete)) {
+    return false;  // Caller will set errorCode.
+  }
+  m_irqstat = SDHC_IRQSTAT;
+  SDHC_IRQSTAT = m_irqstat;
+
+  return (m_irqstat & SDHC_IRQSTAT_CC) &&
+         !(m_irqstat & SDHC_IRQSTAT_CMD_ERROR);
+}
+//------------------------------------------------------------------------------
+static bool cardACMD51(scr_t* scr) {
+  // ACMD51 returns 8 bytes.
+  if (waitTimeout(isBusyCMD13)) {
+    return sdError(SD_CARD_ERROR_CMD13);
+  }
+  enableDmaIrs();
+  SDHC_DSADDR  = (uint32_t)scr;
+  SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(1) | SDHC_BLKATTR_BLKSIZE(8);
+  SDHC_IRQSIGEN = SDHC_IRQSIGEN_MASK;
+  if (!cardAcmd(m_rca, ACMD51_XFERTYP, 0)) {
+    return sdError(SD_CARD_ERROR_ACMD51);
+  }
+  if (!waitDmaStatus()) {
+    return sdError(SD_CARD_ERROR_DMA);
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+static void enableDmaIrs() {
+  m_dmaBusy = true;
+  m_irqstat = 0;
+}
+//------------------------------------------------------------------------------
+static void initSDHC() {
+  initClock();
+
+  // Disable GPIO clock.
+  enableGPIO(false);
+
+#if defined (__IMXRT1062__)
+  SDHC_MIX_CTRL |= 0x80000000;
+#endif  //  (__IMXRT1062__)
+
+  // Reset SDHC. Use default Water Mark Level of 16.
+  SDHC_SYSCTL |= SDHC_SYSCTL_RSTA | SDHC_SYSCTL_SDCLKFS(0x80);
+
+  while (SDHC_SYSCTL & SDHC_SYSCTL_RSTA) {
+  }
+
+  // Set initial SCK rate.
+  setSdclk(SD_MAX_INIT_RATE_KHZ);
+
+  enableGPIO(true);
+
+  // Enable desired IRQSTAT bits.
+  SDHC_IRQSTATEN = SDHC_IRQSTATEN_MASK;
+
+  attachInterruptVector(IRQ_SDHC, sdIrs);
+  NVIC_SET_PRIORITY(IRQ_SDHC, 6*16);
+  NVIC_ENABLE_IRQ(IRQ_SDHC);
+
+  // Send 80 clocks to card.
+  SDHC_SYSCTL |= SDHC_SYSCTL_INITA;
+  while (SDHC_SYSCTL & SDHC_SYSCTL_INITA) {
+  }
+}
+//------------------------------------------------------------------------------
+static uint32_t statusCMD13() {
+  return cardCommand(CMD13_XFERTYP, m_rca) ? SDHC_CMDRSP0 : 0;
+}
+//------------------------------------------------------------------------------
+static bool isBusyCMD13() {
+  return !(statusCMD13() & CARD_STATUS_READY_FOR_DATA);
+}
+//------------------------------------------------------------------------------
+static bool isBusyCommandComplete() {
+  return !(SDHC_IRQSTAT & (SDHC_IRQSTAT_CC | SDHC_IRQSTAT_CMD_ERROR));
+}
+//------------------------------------------------------------------------------
+static bool isBusyCommandInhibit() {
+  return SDHC_PRSSTAT & SDHC_PRSSTAT_CIHB;
+}
+//------------------------------------------------------------------------------
+static bool isBusyDat() {
+  return SDHC_PRSSTAT & (1 << 24) ? false : true;
+}
+//------------------------------------------------------------------------------
+static bool isBusyDMA() {
+  return m_dmaBusy;
+}
+//------------------------------------------------------------------------------
+static bool isBusyFifoRead() {
+  return !(SDHC_PRSSTAT & SDHC_PRSSTAT_BREN);
+}
+//------------------------------------------------------------------------------
+static bool isBusyFifoWrite() {
+  return !(SDHC_PRSSTAT & SDHC_PRSSTAT_BWEN);
+}
+//------------------------------------------------------------------------------
+static bool isBusyTransferComplete() {
+  return !(SDHC_IRQSTAT & (SDHC_IRQSTAT_TC | SDHC_IRQSTAT_ERROR));
+}
+//------------------------------------------------------------------------------
+static bool rdWrSectors(uint32_t xfertyp,
+                       uint32_t sector, uint8_t* buf, size_t n) {
+  if ((3 & (uint32_t)buf) || n == 0) {
+    return sdError(SD_CARD_ERROR_DMA);
+  }
+  if (yieldTimeout(isBusyCMD13)) {
+    return sdError(SD_CARD_ERROR_CMD13);
+  }
+  enableDmaIrs();
+  SDHC_DSADDR  = (uint32_t)buf;
+  SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(n) | SDHC_BLKATTR_BLKSIZE(512);
+  SDHC_IRQSIGEN = SDHC_IRQSIGEN_MASK;
+  if (!cardCommand(xfertyp, m_highCapacity ? sector : 512*sector)) {
+    return false;
+  }
+  return waitDmaStatus();
+}
+//------------------------------------------------------------------------------
+// Read 16 byte CID or CSD register.
+static bool readReg16(uint32_t xfertyp, void* data) {
+  uint8_t* d = reinterpret_cast<uint8_t*>(data);
+  if (!cardCommand(xfertyp, m_rca)) {
+    return false;  // Caller will set errorCode.
+  }
+  uint32_t sr[] = {SDHC_CMDRSP0, SDHC_CMDRSP1, SDHC_CMDRSP2, SDHC_CMDRSP3};
+  for (int i = 0; i < 15; i++) {
+    d[14 - i] = sr[i/4] >> 8*(i%4);
+  }
+  d[15] = 0;
+  return true;
+}
+//------------------------------------------------------------------------------
+static void setSdclk(uint32_t kHzMax) {
+  const uint32_t DVS_LIMIT = 0X10;
+  const uint32_t SDCLKFS_LIMIT = 0X100;
+  uint32_t dvs = 1;
+  uint32_t sdclkfs = 1;
+  uint32_t maxSdclk = 1000*kHzMax;
+  uint32_t base = baseClock();
+
+  while ((base/(sdclkfs*DVS_LIMIT) > maxSdclk) && (sdclkfs < SDCLKFS_LIMIT)) {
+    sdclkfs <<= 1;
+  }
+  while ((base/(sdclkfs*dvs) > maxSdclk) && (dvs < DVS_LIMIT)) {
+    dvs++;
+  }
+  m_sdClkKhz = base/(1000*sdclkfs*dvs);
+  sdclkfs >>= 1;
+  dvs--;
+#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
+  // Disable SDHC clock.
+  SDHC_SYSCTL &= ~SDHC_SYSCTL_SDCLKEN;
+#endif  // defined(__MK64FX512__) || defined(__MK66FX1M0__)
+
+  // Change dividers.
+  uint32_t sysctl = SDHC_SYSCTL & ~(SDHC_SYSCTL_DTOCV_MASK
+                    | SDHC_SYSCTL_DVS_MASK | SDHC_SYSCTL_SDCLKFS_MASK);
+
+  SDHC_SYSCTL = sysctl | SDHC_SYSCTL_DTOCV(0x0E) | SDHC_SYSCTL_DVS(dvs)
+                | SDHC_SYSCTL_SDCLKFS(sdclkfs);
+
+  // Wait until the SDHC clock is stable.
+  while (!(SDHC_PRSSTAT & SDHC_PRSSTAT_SDSTB)) {
+  }
+
+#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
+  // Enable the SDHC clock.
+  SDHC_SYSCTL |= SDHC_SYSCTL_SDCLKEN;
+#endif  // defined(__MK64FX512__) || defined(__MK66FX1M0__)
+}
+//------------------------------------------------------------------------------
+static bool transferStop() {
+  // This fix allows CDIHB to be cleared in Tennsy 3.x without a reset.
+  SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ;
+  if (!cardCommand(CMD12_XFERTYP, 0)) {
+    return sdError(SD_CARD_ERROR_CMD12);
+  }
+  if (yieldTimeout(isBusyDat)) {
+    return sdError(SD_CARD_ERROR_CMD13);
+  }
+  if (SDHC_PRSSTAT & SDHC_PRSSTAT_CDIHB) {
+    // This should not happen after above fix.
+    // Save registers before reset DAT lines.
+    uint32_t irqsststen = SDHC_IRQSTATEN;
+    uint32_t proctl = SDHC_PROCTL & ~SDHC_PROCTL_SABGREQ;
+    // Do reset to clear CDIHB.  Should be a better way!
+    SDHC_SYSCTL |= SDHC_SYSCTL_RSTD;
+    // Restore registers.
+    SDHC_IRQSTATEN = irqsststen;
+    SDHC_PROCTL = proctl;
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+// Return true if timeout occurs.
+static bool yieldTimeout(bool (*fcn)()) {
+  m_busyFcn = fcn;
+  uint32_t m = micros();
+  while (fcn()) {
+    if ((micros() - m) > BUSY_TIMEOUT_MICROS) {
+      m_busyFcn = 0;
+      return true;
+    }
+    yield();
+  }
+  m_busyFcn = 0;
+  return false;  // Caller will set errorCode.
+}
+//------------------------------------------------------------------------------
+static bool waitDmaStatus() {
+  if (yieldTimeout(isBusyDMA)) {
+    return false;  // Caller will set errorCode.
+  }
+  return (m_irqstat & SDHC_IRQSTAT_TC) && !(m_irqstat & SDHC_IRQSTAT_ERROR);
+}
+//------------------------------------------------------------------------------
+// Return true if timeout occurs.
+static bool waitTimeout(bool (*fcn)()) {
+  uint32_t m = micros();
+  while (fcn()) {
+    if ((micros() - m) > BUSY_TIMEOUT_MICROS) {
+      return true;
+    }
+  }
+  return false;  // Caller will set errorCode.
+}
+//------------------------------------------------------------------------------
+static bool waitTransferComplete() {
+  if (!m_transferActive) {
+    return true;
+  }
+  bool timeOut = waitTimeout(isBusyTransferComplete);
+  m_transferActive = false;
+  m_irqstat = SDHC_IRQSTAT;
+  SDHC_IRQSTAT = m_irqstat;
+  if (timeOut || (m_irqstat & SDHC_IRQSTAT_ERROR)) {
+    return sdError(SD_CARD_ERROR_TRANSFER_COMPLETE);
+  }
+  return true;
+}
+//==============================================================================
+// Start of SdioCard member functions.
+//==============================================================================
+bool SdioCard::begin(SdioConfig sdioConfig) {
+  uint32_t kHzSdClk;
+  uint32_t arg;
+  m_sdioConfig = sdioConfig;
+  m_curState = IDLE_STATE;
+  m_initDone = false;
+  m_errorCode = SD_CARD_ERROR_NONE;
+  m_highCapacity = false;
+  m_version2 = false;
+
+  // initialize controller.
+  initSDHC();
+  if (!cardCommand(CMD0_XFERTYP, 0)) {
+    return sdError(SD_CARD_ERROR_CMD0);
+  }
+  // Try several times for case of reset delay.
+  for (uint32_t i = 0; i < CMD8_RETRIES; i++) {
+    if (cardCommand(CMD8_XFERTYP, 0X1AA)) {
+      if (SDHC_CMDRSP0 != 0X1AA) {
+        return sdError(SD_CARD_ERROR_CMD8);
+      }
+      m_version2 = true;
+      break;
+    }
+    SDHC_SYSCTL |= SDHC_SYSCTL_RSTA;
+    while (SDHC_SYSCTL & SDHC_SYSCTL_RSTA) {}
+  }
+  // Must support 3.2-3.4 Volts
+  arg = m_version2 ? 0X40300000 : 0x00300000;
+  int m = micros();
+  do {
+    if (!cardAcmd(0, ACMD41_XFERTYP, arg) ||
+       ((micros() - m) > BUSY_TIMEOUT_MICROS)) {
+      return sdError(SD_CARD_ERROR_ACMD41);
+    }
+  } while ((SDHC_CMDRSP0 & 0x80000000) == 0);
+  m_ocr = SDHC_CMDRSP0;
+  if (SDHC_CMDRSP0 & 0x40000000) {
+    // Is high capacity.
+    m_highCapacity = true;
+  }
+  if (!cardCommand(CMD2_XFERTYP, 0)) {
+    return sdError(SD_CARD_ERROR_CMD2);
+  }
+  if (!cardCommand(CMD3_XFERTYP, 0)) {
+    return sdError(SD_CARD_ERROR_CMD3);
+  }
+  m_rca = SDHC_CMDRSP0 & 0xFFFF0000;
+
+  if (!readReg16(CMD9_XFERTYP, &m_csd)) {
+    return sdError(SD_CARD_ERROR_CMD9);
+  }
+  if (!readReg16(CMD10_XFERTYP, &m_cid)) {
+    return sdError(SD_CARD_ERROR_CMD10);
+  }
+  if (!cardCommand(CMD7_XFERTYP, m_rca)) {
+    return sdError(SD_CARD_ERROR_CMD7);
+  }
+  // Set card to bus width four.
+  if (!cardAcmd(m_rca, ACMD6_XFERTYP, 2)) {
+    return sdError(SD_CARD_ERROR_ACMD6);
+  }
+  // Set SDHC to bus width four.
+  SDHC_PROCTL &= ~SDHC_PROCTL_DTW_MASK;
+  SDHC_PROCTL |= SDHC_PROCTL_DTW(SDHC_PROCTL_DTW_4BIT);
+
+  SDHC_WML = SDHC_WML_RDWML(FIFO_WML) | SDHC_WML_WRWML(FIFO_WML);
+
+  if (!cardACMD51(&m_scr)) {
+    return false;
+  }
+  // Determine if High Speed mode is supported and set frequency.
+  // Check status[16] for error 0XF or status[16] for new mode 0X1.
+  uint8_t status[64];
+  if (m_scr.sdSpec() > 0 &&
+      cardCMD6(0X00FFFFFF, status) && (2 & status[13]) &&
+      cardCMD6(0X80FFFFF1, status) && (status[16] & 0XF) == 1) {
+    kHzSdClk = 50000;
+  } else {
+    kHzSdClk = 25000;
+  }
+  // Disable GPIO.
+  enableGPIO(false);
+
+  // Set the SDHC SCK frequency.
+  setSdclk(kHzSdClk);
+
+  // Enable GPIO.
+  enableGPIO(true);
+  m_initDone = true;
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::cardCMD6(uint32_t arg, uint8_t* status) {
+  // CMD6 returns 64 bytes.
+  if (waitTimeout(isBusyCMD13)) {
+    return sdError(SD_CARD_ERROR_CMD13);
+  }
+  enableDmaIrs();
+  SDHC_DSADDR  = (uint32_t)status;
+  SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(1) | SDHC_BLKATTR_BLKSIZE(64);
+  SDHC_IRQSIGEN = SDHC_IRQSIGEN_MASK;
+  if (!cardCommand(CMD6_XFERTYP, arg)) {
+    return sdError(SD_CARD_ERROR_CMD6);
+  }
+  if (!waitDmaStatus()) {
+    return sdError(SD_CARD_ERROR_DMA);
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::erase(uint32_t firstSector, uint32_t lastSector) {
+  if (m_curState != IDLE_STATE && !syncDevice()) {
+    return false;
+  }
+  // check for single sector erase
+  if (!m_csd.eraseSingleBlock()) {
+    // erase size mask
+    uint8_t m = m_csd.eraseSize() - 1;
+    if ((firstSector & m) != 0 || ((lastSector + 1) & m) != 0) {
+      // error card can't erase specified area
+      return sdError(SD_CARD_ERROR_ERASE_SINGLE_SECTOR);
+    }
+  }
+  if (!m_highCapacity) {
+    firstSector <<= 9;
+    lastSector <<= 9;
+  }
+  if (!cardCommand(CMD32_XFERTYP, firstSector)) {
+    return sdError(SD_CARD_ERROR_CMD32);
+  }
+  if (!cardCommand(CMD33_XFERTYP, lastSector)) {
+     return sdError(SD_CARD_ERROR_CMD33);
+  }
+  if (!cardCommand(CMD38_XFERTYP, 0)) {
+    return sdError(SD_CARD_ERROR_CMD38);
+  }
+  if (waitTimeout(isBusyCMD13)) {
+    return sdError(SD_CARD_ERROR_ERASE_TIMEOUT);
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+uint8_t SdioCard::errorCode() const {
+  return m_errorCode;
+}
+//------------------------------------------------------------------------------
+uint32_t SdioCard::errorData() const {
+  return m_irqstat;
+}
+//------------------------------------------------------------------------------
+uint32_t SdioCard::errorLine() const {
+  return m_errorLine;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::isBusy() {
+  if (m_sdioConfig.useDma()) {
+    return m_busyFcn ? m_busyFcn() : m_initDone && isBusyCMD13();
+  } else {
+    if (m_transferActive) {
+      if (isBusyTransferComplete()) {
+        return true;
+      }
+#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
+      if ((SDHC_BLKATTR & 0XFFFF0000) != 0) {
+        return false;
+      }
+      m_transferActive = false;
+      stopTransmission(false);
+      return true;
+#else  // defined(__MK64FX512__) || defined(__MK66FX1M0__)
+      return false;
+#endif  // defined(__MK64FX512__) || defined(__MK66FX1M0__)
+    }
+    // Use DAT0 low as busy.
+    return SDHC_PRSSTAT & (1 << 24) ? false : true;
+  }
+}
+//------------------------------------------------------------------------------
+uint32_t SdioCard::kHzSdClk() {
+  return m_sdClkKhz;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::readCID(cid_t* cid) {
+  memcpy(cid, &m_cid, 16);
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::readCSD(csd_t* csd) {
+  memcpy(csd, &m_csd, 16);
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::readData(uint8_t* dst) {
+  DBG_IRQSTAT();
+  uint32_t* p32 = reinterpret_cast<uint32_t*>(dst);
+
+  if (!(SDHC_PRSSTAT & SDHC_PRSSTAT_RTA)) {
+    SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ;
+    noInterrupts();
+    SDHC_PROCTL |= SDHC_PROCTL_CREQ;
+    SDHC_PROCTL |= SDHC_PROCTL_SABGREQ;
+    interrupts();
+  }
+  if (waitTimeout(isBusyFifoRead)) {
+    return sdError(SD_CARD_ERROR_READ_FIFO);
+  }
+  for (uint32_t iw = 0 ; iw < 512/(4*FIFO_WML); iw++) {
+    while (0 == (SDHC_PRSSTAT & SDHC_PRSSTAT_BREN)) {
+    }
+    for (uint32_t i = 0; i < FIFO_WML; i++) {
+      p32[i] = SDHC_DATPORT;
+    }
+    p32 += FIFO_WML;
+  }
+  if (waitTimeout(isBusyTransferComplete)) {
+    return sdError(SD_CARD_ERROR_READ_TIMEOUT);
+  }
+  m_irqstat = SDHC_IRQSTAT;
+  SDHC_IRQSTAT = m_irqstat;
+  return (m_irqstat & SDHC_IRQSTAT_TC) && !(m_irqstat & SDHC_IRQSTAT_ERROR);
+}
+//------------------------------------------------------------------------------
+bool SdioCard::readOCR(uint32_t* ocr) {
+  *ocr = m_ocr;
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::readSCR(scr_t* scr) {
+  memcpy(scr, &m_scr, 8);
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::readSector(uint32_t sector, uint8_t* dst) {
+  if (m_sdioConfig.useDma()) {
+    uint8_t aligned[512];
+
+    uint8_t* ptr = (uint32_t)dst & 3 ? aligned : dst;
+
+    if (!rdWrSectors(CMD17_DMA_XFERTYP, sector, ptr, 1)) {
+      return sdError(SD_CARD_ERROR_CMD17);
+    }
+    if (ptr != dst) {
+      memcpy(dst, aligned, 512);
+    }
+  } else {
+    if (!waitTransferComplete()) {
+      return false;
+    }
+    if (m_curState != READ_STATE || sector != m_curSector) {
+      if (!syncDevice()) {
+        return false;
+      }
+      if (!readStart(sector)) {
+        return false;
+      }
+      m_curSector = sector;
+      m_curState = READ_STATE;
+    }
+    if (!readData(dst)) {
+      return false;
+    }
+#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
+    if ((SDHC_BLKATTR & 0XFFFF0000) == 0) {
+      if (!syncDevice()) {
+        return false;
+      }
+    }
+#endif  // defined(__MK64FX512__) || defined(__MK66FX1M0__)
+    m_curSector++;
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::readSectors(uint32_t sector, uint8_t* dst, size_t n) {
+  if (m_sdioConfig.useDma()) {
+    if ((uint32_t)dst & 3) {
+      for (size_t i = 0; i < n; i++, sector++, dst += 512) {
+        if (!readSector(sector, dst)) {
+          return false;  // readSector will set errorCode.
+        }
+      }
+      return true;
+    }
+    if (!rdWrSectors(CMD18_DMA_XFERTYP, sector, dst, n)) {
+      return sdError(SD_CARD_ERROR_CMD18);
+    }
+  } else {
+    for (size_t i = 0; i < n; i++) {
+      if (!readSector(sector + i, dst + i*512UL)) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+// SDHC will do Auto CMD12 after count sectors.
+bool SdioCard::readStart(uint32_t sector) {
+  DBG_IRQSTAT();
+  if (yieldTimeout(isBusyCMD13)) {
+    return sdError(SD_CARD_ERROR_CMD13);
+  }
+  SDHC_PROCTL |= SDHC_PROCTL_SABGREQ;
+#if defined(__IMXRT1062__)
+  // Infinite transfer.
+  SDHC_BLKATTR = SDHC_BLKATTR_BLKSIZE(512);
+#else  // defined(__IMXRT1062__)
+  // Errata - can't do infinite transfer.
+  SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(MAX_BLKCNT) | SDHC_BLKATTR_BLKSIZE(512);
+#endif  // defined(__IMXRT1062__)
+
+  if (!cardCommand(CMD18_PGM_XFERTYP, m_highCapacity ? sector : 512*sector)) {
+    return sdError(SD_CARD_ERROR_CMD18);
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::readStop() {
+  return transferStop();
+}
+//------------------------------------------------------------------------------
+uint32_t SdioCard::sectorCount() {
+  return m_csd.capacity();
+}
+//------------------------------------------------------------------------------
+uint32_t SdioCard::status() {
+  return statusCMD13();
+}
+//------------------------------------------------------------------------------
+bool SdioCard::stopTransmission(bool blocking) {
+  m_curState = IDLE_STATE;
+  // This fix allows CDIHB to be cleared in Tennsy 3.x without a reset.
+  SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ;
+  if (!cardCommand(CMD12_XFERTYP, 0)) {
+    return sdError(SD_CARD_ERROR_CMD12);
+  }
+  if (blocking) {
+    if (yieldTimeout(isBusyDat)) {
+      return sdError(SD_CARD_ERROR_CMD13);
+    }
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::syncDevice() {
+  if (!waitTransferComplete()) {
+    return false;
+  }
+  if (m_curState != IDLE_STATE) {
+    return stopTransmission(true);
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+uint8_t SdioCard::type() const {
+  return  m_version2 ? m_highCapacity ?
+          SD_CARD_TYPE_SDHC : SD_CARD_TYPE_SD2 : SD_CARD_TYPE_SD1;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::writeData(const uint8_t* src) {
+  DBG_IRQSTAT();
+  if (!waitTransferComplete()) {
+    return false;
+  }
+  const uint32_t* p32 = reinterpret_cast<const uint32_t*>(src);
+  if (!(SDHC_PRSSTAT & SDHC_PRSSTAT_WTA)) {
+    SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ;
+    SDHC_PROCTL |= SDHC_PROCTL_CREQ;
+  }
+  SDHC_PROCTL |= SDHC_PROCTL_SABGREQ;
+  if (waitTimeout(isBusyFifoWrite)) {
+    return sdError(SD_CARD_ERROR_WRITE_FIFO);
+  }
+  for (uint32_t iw = 0 ; iw < 512/(4*FIFO_WML); iw++) {
+    while (0 == (SDHC_PRSSTAT & SDHC_PRSSTAT_BWEN)) {
+    }
+    for (uint32_t i = 0; i < FIFO_WML; i++) {
+      SDHC_DATPORT = p32[i];
+    }
+    p32 += FIFO_WML;
+  }
+  m_transferActive = true;
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::writeSector(uint32_t sector, const uint8_t* src) {
+  if (m_sdioConfig.useDma()) {
+    uint8_t* ptr;
+    uint8_t aligned[512];
+    if (3 & (uint32_t)src) {
+      ptr = aligned;
+      memcpy(aligned, src, 512);
+    } else {
+      ptr = const_cast<uint8_t*>(src);
+    }
+    if (!rdWrSectors(CMD24_DMA_XFERTYP, sector, ptr, 1)) {
+      return sdError(SD_CARD_ERROR_CMD24);
+    }
+  } else {
+    if (!waitTransferComplete()) {
+      return false;
+    }
+#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
+    // End transfer with CMD12 if required.
+    if ((SDHC_BLKATTR & 0XFFFF0000) == 0) {
+      if (!syncDevice()) {
+        return false;
+      }
+    }
+#endif  // defined(__MK64FX512__) || defined(__MK66FX1M0__)
+    if (m_curState != WRITE_STATE || m_curSector != sector) {
+      if (!syncDevice()) {
+        return false;
+      }
+      if (!writeStart(sector )) {
+        return false;
+      }
+      m_curSector = sector;
+      m_curState = WRITE_STATE;
+    }
+    if (!writeData(src)) {
+      return false;
+    }
+    m_curSector++;
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::writeSectors(uint32_t sector, const uint8_t* src, size_t n) {
+  if (m_sdioConfig.useDma()) {
+    uint8_t* ptr = const_cast<uint8_t*>(src);
+    if (3 & (uint32_t)ptr) {
+      for (size_t i = 0; i < n; i++, sector++, ptr += 512) {
+        if (!writeSector(sector, ptr)) {
+          return false;  // writeSector will set errorCode.
+        }
+      }
+      return true;
+    }
+    if (!rdWrSectors(CMD25_DMA_XFERTYP, sector, ptr, n)) {
+      return sdError(SD_CARD_ERROR_CMD25);
+    }
+  } else {
+    for (size_t i = 0; i < n; i++) {
+      if (!writeSector(sector + i, src + i*512UL)) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::writeStart(uint32_t sector) {
+  if (yieldTimeout(isBusyCMD13)) {
+    return sdError(SD_CARD_ERROR_CMD13);
+  }
+  SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ;
+
+#if defined(__IMXRT1062__)
+  // Infinite transfer.
+  SDHC_BLKATTR = SDHC_BLKATTR_BLKSIZE(512);
+#else  // defined(__IMXRT1062__)
+  // Errata - can't do infinite transfer.
+  SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(MAX_BLKCNT) | SDHC_BLKATTR_BLKSIZE(512);
+#endif  // defined(__IMXRT1062__)
+  if (!cardCommand(CMD25_PGM_XFERTYP, m_highCapacity ? sector : 512*sector)) {
+    return sdError(SD_CARD_ERROR_CMD25);
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+bool SdioCard::writeStop() {
+  return transferStop();
+}
+#endif  // defined(__MK64FX512__)  defined(__MK66FX1M0__) defined(__IMXRT1062__)

+ 514 - 0
lib/SdFat_NoArduino/src/SdFat.h

@@ -0,0 +1,514 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef SdFat_h
+#define SdFat_h
+/**
+ * \file
+ * \brief main SdFs include file.
+ */
+#include "common/SysCall.h"
+#include "SdCard/SdCard.h"
+#include "ExFatLib/ExFatLib.h"
+#include "FatLib/FatLib.h"
+#include "FsLib/FsLib.h"
+#if INCLUDE_SDIOS
+#include "sdios.h"
+#endif  // INCLUDE_SDIOS
+//------------------------------------------------------------------------------
+/** SdFat version for cpp use. */
+#define SD_FAT_VERSION 20200
+/** SdFat version as string. */
+#define SD_FAT_VERSION_STR "2.2.0"
+//==============================================================================
+/**
+ * \class SdBase
+ * \brief base SD file system template class.
+ */
+template <class Vol, class Fmt>
+class SdBase : public Vol {
+ public:
+  //----------------------------------------------------------------------------
+  /** Initialize SD card and file system.
+   *
+   * \param[in] csPin SD card chip select pin.
+   * \return true for success or false for failure.
+   */
+  bool begin(SdCsPin_t csPin = SS) {
+#ifdef BUILTIN_SDCARD
+    if (csPin == BUILTIN_SDCARD) {
+      return begin(SdioConfig(FIFO_SDIO));
+    }
+#endif  // BUILTIN_SDCARD
+    return begin(SdSpiConfig(csPin, SHARED_SPI));
+  }
+  //----------------------------------------------------------------------------
+  /** Initialize SD card and file system.
+   *
+   * \param[in] csPin SD card chip select pin.
+   * \param[in] maxSck Maximum SCK frequency.
+   * \return true for success or false for failure.
+   */
+  bool begin(SdCsPin_t csPin, uint32_t maxSck) {
+    return begin(SdSpiConfig(csPin, SHARED_SPI, maxSck));
+  }
+  //----------------------------------------------------------------------------
+  /** Initialize SD card and file system for SPI mode.
+   *
+   * \param[in] spiConfig SPI configuration.
+   * \return true for success or false for failure.
+   */
+  bool begin(SdSpiConfig spiConfig) {
+    return cardBegin(spiConfig) && Vol::begin(m_card);
+  }
+  //---------------------------------------------------------------------------
+  /** Initialize SD card and file system for SDIO mode.
+   *
+   * \param[in] sdioConfig SDIO configuration.
+   * \return true for success or false for failure.
+   */
+  bool begin(SdioConfig sdioConfig) {
+    return cardBegin(sdioConfig) && Vol::begin(m_card);
+  }
+  //----------------------------------------------------------------------------
+  /** \return Pointer to SD card object. */
+  SdCard* card() {return m_card;}
+  //----------------------------------------------------------------------------
+  /** Initialize SD card in SPI mode.
+   *
+   * \param[in] spiConfig SPI configuration.
+   * \return true for success or false for failure.
+   */
+  bool cardBegin(SdSpiConfig spiConfig) {
+    m_card = m_cardFactory.newCard(spiConfig);
+    return m_card && !m_card->errorCode();
+  }
+  //----------------------------------------------------------------------------
+  /** Initialize SD card in SDIO mode.
+   *
+   * \param[in] sdioConfig SDIO configuration.
+   * \return true for success or false for failure.
+   */
+  bool cardBegin(SdioConfig sdioConfig) {
+    m_card = m_cardFactory.newCard(sdioConfig);
+    return m_card && !m_card->errorCode();
+  }
+  //----------------------------------------------------------------------------
+  /** End use of card. */
+  void end() {
+    Vol::end();
+    if (m_card) {
+      m_card->end();
+    }
+  }
+  //----------------------------------------------------------------------------
+  /** %Print error info and halt.
+   *
+   * \param[in] pr Print destination.
+   */
+  void errorHalt(print_t* pr) {
+    if (sdErrorCode()) {
+      pr->print(F("SdError: 0X"));
+      pr->print(sdErrorCode(), HEX);
+      pr->print(F(",0X"));
+      pr->println(sdErrorData(), HEX);
+    } else if (!Vol::fatType()) {
+      pr->println(F("Check SD format."));
+    }
+    while (true) {}
+  }
+  //----------------------------------------------------------------------------
+  /** %Print error info and halt.
+   *
+   * \param[in] pr Print destination.
+   * \param[in] msg Message to print.
+   */
+  void errorHalt(print_t* pr, const char* msg) {
+    pr->print(F("error: "));
+    pr->println(msg);
+    errorHalt(pr);
+  }
+  //----------------------------------------------------------------------------
+  /** %Print msg and halt.
+   *
+   * \param[in] pr Print destination.
+   * \param[in] msg Message to print.
+   */
+  void errorHalt(print_t* pr, const __FlashStringHelper* msg) {
+    pr->print(F("error: "));
+    pr->println(msg);
+    errorHalt(pr);
+  }
+  //----------------------------------------------------------------------------
+  /** Format SD card
+   *
+   * \param[in] pr Print destination.
+   * \return true for success else false.
+   */
+  bool format(print_t* pr = nullptr) {
+    Fmt fmt;
+    uint8_t* mem = Vol::end();
+    if (!mem) {
+      return false;
+    }
+    bool switchSpi = hasDedicatedSpi() && !isDedicatedSpi();
+    if (switchSpi && !setDedicatedSpi(true)) {
+      return false;
+    }
+    bool rtn = fmt.format(card(), mem, pr);
+    if (switchSpi && !setDedicatedSpi(false)) {
+      return false;
+    }
+    return rtn;
+  }
+  //----------------------------------------------------------------------------
+  /** \return the free cluster count. */
+  uint32_t freeClusterCount() {
+    bool switchSpi = hasDedicatedSpi() && !isDedicatedSpi();
+    if (switchSpi && !setDedicatedSpi(true)) {
+      return 0;
+    }
+    uint32_t rtn = Vol::freeClusterCount();
+    if (switchSpi && !setDedicatedSpi(false)) {
+      return 0;
+    }
+    return rtn;
+  }
+  //----------------------------------------------------------------------------
+  /** \return true if can be in dedicated SPI state */
+  bool hasDedicatedSpi() {return m_card ? m_card->hasDedicatedSpi() : false;}
+  //----------------------------------------------------------------------------
+  /** %Print error info and halt.
+   *
+   * \param[in] pr Print destination.
+   */
+  void initErrorHalt(print_t* pr) {
+    initErrorPrint(pr);
+    while (true) {}
+  }
+  //----------------------------------------------------------------------------
+  /** %Print error info and halt.
+   *
+   * \param[in] pr Print destination.
+   * \param[in] msg Message to print.
+   */
+  void initErrorHalt(print_t* pr, const char* msg) {
+    pr->println(msg);
+    initErrorHalt(pr);
+  }
+  //----------------------------------------------------------------------------
+  /** %Print error info and halt.
+   *
+   * \param[in] pr Print destination.
+   * \param[in] msg Message to print.
+   */
+  void initErrorHalt(print_t* pr, const __FlashStringHelper* msg) {
+    pr->println(msg);
+    initErrorHalt(pr);
+  }
+  //----------------------------------------------------------------------------
+  /** Print error details after begin() fails.
+   *
+   * \param[in] pr Print destination.
+   */
+  void initErrorPrint(print_t* pr) {
+    pr->println(F("begin() failed"));
+    if (sdErrorCode()) {
+      pr->println(F("Do not reformat the SD."));
+      if (sdErrorCode() == SD_CARD_ERROR_CMD0) {
+        pr->println(F("No card, wrong chip select pin, or wiring error?"));
+      }
+    }
+    errorPrint(pr);
+  }
+  //----------------------------------------------------------------------------
+  /** \return true if in dedicated SPI state. */
+  bool isDedicatedSpi() {return m_card ? m_card->isDedicatedSpi() : false;}
+  //----------------------------------------------------------------------------
+  /** %Print volume FAT/exFAT type.
+   *
+   * \param[in] pr Print destination.
+   */
+  void printFatType(print_t* pr) {
+    if (Vol::fatType() == FAT_TYPE_EXFAT) {
+      pr->print(F("exFAT"));
+    } else {
+      pr->print(F("FAT"));
+      pr->print(Vol::fatType());
+    }
+  }
+  //----------------------------------------------------------------------------
+  /** %Print SD errorCode and errorData.
+   *
+   * \param[in] pr Print destination.
+   */
+  void errorPrint(print_t* pr) {
+    if (sdErrorCode()) {
+      pr->print(F("SdError: 0X"));
+      pr->print(sdErrorCode(), HEX);
+      pr->print(F(",0X"));
+      pr->println(sdErrorData(), HEX);
+    } else if (!Vol::fatType()) {
+      pr->println(F("Check SD format."));
+    }
+  }
+  //----------------------------------------------------------------------------
+  /** %Print msg, any SD error code.
+   *
+   * \param[in] pr Print destination.
+   * \param[in] msg Message to print.
+   */
+  void errorPrint(print_t* pr, char const* msg) {
+    pr->print(F("error: "));
+    pr->println(msg);
+    errorPrint(pr);
+  }
+
+  /** %Print msg, any SD error code.
+   *
+   * \param[in] pr Print destination.
+   * \param[in] msg Message to print.
+   */
+  void errorPrint(print_t* pr, const __FlashStringHelper* msg) {
+    pr->print(F("error: "));
+    pr->println(msg);
+    errorPrint(pr);
+  }
+  //----------------------------------------------------------------------------
+  /** %Print error info and return.
+   *
+   * \param[in] pr Print destination.
+   */
+  void printSdError(print_t* pr) {
+    if (sdErrorCode()) {
+      if (sdErrorCode() == SD_CARD_ERROR_CMD0) {
+        pr->println(F("No card, wrong chip select pin, or wiring error?"));
+      }
+      pr->print(F("SD error: "));
+      printSdErrorSymbol(pr, sdErrorCode());
+      pr->print(F(" = 0x"));
+      pr->print(sdErrorCode(), HEX);
+      pr->print(F(",0x"));
+      pr->println(sdErrorData(), HEX);
+    } else if (!Vol::fatType()) {
+      pr->println(F("Check SD format."));
+    }
+  }
+  //----------------------------------------------------------------------------
+  /** \return SD card error code. */
+  uint8_t sdErrorCode() {
+    if (m_card) {
+      return m_card->errorCode();
+    }
+    return SD_CARD_ERROR_INVALID_CARD_CONFIG;
+  }
+  //----------------------------------------------------------------------------
+  /** \return SD card error data. */
+  uint8_t sdErrorData() {return m_card ? m_card->errorData() : 0;}
+  //----------------------------------------------------------------------------
+  /** Set SPI sharing state
+   * \param[in] value desired state.
+   * \return true for success else false;
+   */
+  bool setDedicatedSpi(bool value) {
+    if (m_card) {
+      return m_card->setDedicatedSpi(value);
+    }
+    return false;
+  }
+  //----------------------------------------------------------------------------
+  /** \return pointer to base volume */
+  Vol* vol() {return reinterpret_cast<Vol*>(this);}
+  //----------------------------------------------------------------------------
+  /** Initialize file system after call to cardBegin.
+   *
+   * \return true for success or false for failure.
+   */
+  bool volumeBegin() {
+     return Vol::begin(m_card);
+  }
+#if ENABLE_ARDUINO_SERIAL
+  /** Print error details after begin() fails. */
+  void initErrorPrint() {
+    initErrorPrint(&Serial);
+  }
+  //----------------------------------------------------------------------------
+  /** %Print msg to Serial and halt.
+   *
+   * \param[in] msg Message to print.
+   */
+  void errorHalt(const __FlashStringHelper* msg) {
+    errorHalt(&Serial, msg);
+  }
+  //----------------------------------------------------------------------------
+  /** %Print error info to Serial and halt. */
+  void errorHalt() {errorHalt(&Serial);}
+  //----------------------------------------------------------------------------
+  /** %Print error info and halt.
+   *
+   * \param[in] msg Message to print.
+   */
+  void errorHalt(const char* msg) {errorHalt(&Serial, msg);}
+  //----------------------------------------------------------------------------
+  /** %Print error info and halt. */
+  void initErrorHalt() {initErrorHalt(&Serial);}
+  //----------------------------------------------------------------------------
+  /** %Print msg, any SD error code.
+   *
+   * \param[in] msg Message to print.
+   */
+  void errorPrint(const char* msg) {errorPrint(&Serial, msg);}
+   /** %Print msg, any SD error code.
+   *
+   * \param[in] msg Message to print.
+   */
+  void errorPrint(const __FlashStringHelper* msg) {errorPrint(&Serial, msg);}
+  //----------------------------------------------------------------------------
+  /** %Print error info and halt.
+   *
+   * \param[in] msg Message to print.
+   */
+  void initErrorHalt(const char* msg) {initErrorHalt(&Serial, msg);}
+  //----------------------------------------------------------------------------
+  /** %Print error info and halt.
+   *
+   * \param[in] msg Message to print.
+   */
+  void initErrorHalt(const __FlashStringHelper* msg) {
+    initErrorHalt(&Serial, msg);
+  }
+#endif  // ENABLE_ARDUINO_SERIAL
+  //----------------------------------------------------------------------------
+ private:
+  SdCard* m_card = nullptr;
+  SdCardFactory m_cardFactory;
+};
+//------------------------------------------------------------------------------
+/**
+ * \class SdFat32
+ * \brief SD file system class for FAT volumes.
+ */
+class SdFat32 : public SdBase<FatVolume, FatFormatter> {
+ public:
+};
+//------------------------------------------------------------------------------
+/**
+ * \class SdExFat
+ * \brief SD file system class for exFAT volumes.
+ */
+class SdExFat : public SdBase<ExFatVolume, ExFatFormatter> {
+ public:
+};
+//------------------------------------------------------------------------------
+/**
+ * \class SdFs
+ * \brief SD file system class for FAT16, FAT32, and exFAT volumes.
+ */
+class SdFs : public SdBase<FsVolume, FsFormatter> {
+ public:
+};
+//------------------------------------------------------------------------------
+#if SDFAT_FILE_TYPE == 1 || defined(DOXYGEN)
+/** Select type for SdFat. */
+typedef SdFat32 SdFat;
+/** Select type for SdBaseFile. */
+typedef FatFile SdBaseFile;
+#elif SDFAT_FILE_TYPE == 2
+typedef SdExFat SdFat;
+typedef ExFatFile SdBaseFile;
+#elif SDFAT_FILE_TYPE == 3
+typedef SdFs SdFat;
+typedef FsBaseFile SdBaseFile;
+#else  // SDFAT_FILE_TYPE
+#error Invalid SDFAT_FILE_TYPE
+#endif  // SDFAT_FILE_TYPE
+//
+// Only define File if FS.h is not included.
+// Line with test for __has_include must not have operators or parentheses.
+#if defined __has_include
+#if __has_include(<FS.h>)
+#define HAS_INCLUDE_FS_H
+#warning File not defined because __has_include(FS.h)
+#endif  // __has_include(<FS.h>)
+#endif  // defined __has_include
+#ifndef HAS_INCLUDE_FS_H
+#if SDFAT_FILE_TYPE == 1 || defined(DOXYGEN)
+/** Select type for File. */
+typedef File32 File;
+#elif SDFAT_FILE_TYPE == 2
+typedef ExFile File;
+#elif SDFAT_FILE_TYPE == 3
+typedef FsFile File;
+#endif  // SDFAT_FILE_TYPE
+#endif  // HAS_INCLUDE_FS_H
+/**
+ * \class SdFile
+ * \brief FAT16/FAT32 file with Print.
+ */
+class SdFile : public PrintFile<SdBaseFile> {
+ public:
+  SdFile() {}
+  /** Create an open SdFile.
+   * \param[in] path path for file.
+   * \param[in] oflag open flags.
+   */
+  SdFile(const char* path, oflag_t oflag) {
+    open(path, oflag);
+  }
+  /** Set the date/time callback function
+   *
+   * \param[in] dateTime The user's call back function.  The callback
+   * function is of the form:
+   *
+   * \code
+   * void dateTime(uint16_t* date, uint16_t* time) {
+   *   uint16_t year;
+   *   uint8_t month, day, hour, minute, second;
+   *
+   *   // User gets date and time from GPS or real-time clock here
+   *
+   *   // return date using FS_DATE macro to format fields
+   *   *date = FS_DATE(year, month, day);
+   *
+   *   // return time using FS_TIME macro to format fields
+   *   *time = FS_TIME(hour, minute, second);
+   * }
+   * \endcode
+   *
+   * Sets the function that is called when a file is created or when
+   * a file's directory entry is modified by sync(). All timestamps,
+   * access, creation, and modify, are set when a file is created.
+   * sync() maintains the last access date and last modify date/time.
+   *
+   */
+  static void dateTimeCallback(
+    void (*dateTime)(uint16_t* date, uint16_t* time)) {
+    FsDateTime::setCallback(dateTime);
+  }
+  /**  Cancel the date/time callback function. */
+  static void dateTimeCallbackCancel() {
+    FsDateTime::clearCallback();
+  }
+};
+#endif  // SdFat_h

+ 435 - 0
lib/SdFat_NoArduino/src/SdFatConfig.h

@@ -0,0 +1,435 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+/**
+ * \file
+ * \brief configuration definitions
+ */
+#ifndef SdFatConfig_h
+#define SdFatConfig_h
+#include <stdint.h>
+#ifdef __AVR__
+#include <avr/io.h>
+#endif  // __AVR__
+//
+// To try UTF-8 encoded filenames.
+// #define USE_UTF8_LONG_NAMES 1
+//
+// For minimum flash size use these settings:
+// #define USE_FAT_FILE_FLAG_CONTIGUOUS 0
+// #define ENABLE_DEDICATED_SPI 0
+// #define USE_LONG_FILE_NAMES 0
+// #define SDFAT_FILE_TYPE 1
+// #define CHECK_FLASH_PROGRAMMING 0  // May cause SD to sleep at high current.
+//
+// Options can be set in a makefile or an IDE like platformIO
+// if they are in a #ifndef/#endif block below.
+//------------------------------------------------------------------------------
+/** For Debug - must be one */
+#define ENABLE_ARDUINO_FEATURES 0
+/** For Debug - must be one */
+#define ENABLE_ARDUINO_SERIAL 0
+/** For Debug - must be one */
+#define ENABLE_ARDUINO_STRING 0
+//------------------------------------------------------------------------------
+#if ENABLE_ARDUINO_FEATURES
+#include "Arduino.h"
+#ifdef PLATFORM_ID
+// Only defined if a Particle device.
+#include "application.h"
+#endif  // PLATFORM_ID
+#else
+#define SS 0
+extern "C" unsigned long millis();
+#endif  // ENABLE_ARDUINO_FEATURES
+//------------------------------------------------------------------------------
+/**
+ * File types for SdFat, File, SdFile, SdBaseFile, fstream,
+ * ifstream, and ofstream.
+ *
+ * Set SDFAT_FILE_TYPE to:
+ *
+ * 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
+ */
+#ifndef SDFAT_FILE_TYPE
+#if defined(__AVR__) && FLASHEND < 0X8000
+// 32K AVR boards.
+#define SDFAT_FILE_TYPE 1
+#else  // defined(__AVR__) && FLASHEND < 0X8000
+// All other boards.
+#define SDFAT_FILE_TYPE 3
+#endif  // defined(__AVR__) && FLASHEND < 0X8000
+#endif  // SDFAT_FILE_TYPE
+//------------------------------------------------------------------------------
+/**
+ * Set USE_FAT_FILE_FLAG_CONTIGUOUS nonzero to optimize access to
+ * contiguous files.  A small amount of flash is flash is used.
+ */
+#ifndef USE_FAT_FILE_FLAG_CONTIGUOUS
+#define USE_FAT_FILE_FLAG_CONTIGUOUS 1
+#endif  // USE_FAT_FILE_FLAG_CONTIGUOUS
+//------------------------------------------------------------------------------
+/**
+ * Set ENABLE_DEDICATED_SPI non-zero to enable dedicated use of the SPI bus.
+ * Selecting dedicated SPI in SdSpiConfig() will produce better
+ * performance by using very large multi-block transfers to and
+ * from the SD card.
+ *
+ * Enabling dedicated SPI will cost extra flash and RAM.
+ */
+#ifndef ENABLE_DEDICATED_SPI
+#if defined(__AVR__) && FLASHEND < 0X8000
+// 32K AVR boards.
+#define ENABLE_DEDICATED_SPI 1
+#else  // defined(__AVR__) && FLASHEND < 0X8000
+// All other boards.
+#define ENABLE_DEDICATED_SPI 1
+#endif  // defined(__AVR__) && FLASHEND < 0X8000
+#endif  // ENABLE_DEDICATED_SPI
+//------------------------------------------------------------------------------
+// Driver options
+/**
+ * If the symbol SPI_DRIVER_SELECT is:
+ *
+ * 0 - An optimized custom SPI driver is used if it exists
+ *     else the standard library driver is used.
+ *
+ * 1 - The standard library driver is always used.
+ *
+ * 2 - An external SPI driver of SoftSpiDriver template class is always used.
+ *
+ * 3 - An external SPI driver derived from SdSpiBaseClass is always used.
+ */
+#ifndef SPI_DRIVER_SELECT
+#define SPI_DRIVER_SELECT 0
+#endif  // SPI_DRIVER_SELECT
+/**
+ * If USE_SPI_ARRAY_TRANSFER is non-zero and the standard SPI library is
+ * use, the array transfer function, transfer(buf, size), will be used.
+ * This option will allocate up to a 512 byte temporary buffer for send.
+ * This may be faster for some boards.  Do not use this with AVR boards.
+ */
+#ifndef USE_SPI_ARRAY_TRANSFER
+#define USE_SPI_ARRAY_TRANSFER 0
+#endif  // USE_SPI_ARRAY_TRANSFER
+/**
+ * SD maximum initialization clock rate.
+ */
+#ifndef SD_MAX_INIT_RATE_KHZ
+#define SD_MAX_INIT_RATE_KHZ 400
+#endif  // SD_MAX_INIT_RATE_KHZ
+/**
+ * Set USE_BLOCK_DEVICE_INTERFACE nonzero to use a generic block device.
+ * This allow use of an external FsBlockDevice driver that is derived from
+ * the FsBlockDeviceInterface like this:
+ *
+ * class UsbMscDriver : public FsBlockDeviceInterface {
+ *   ... code for USB mass storage class driver.
+ * };
+ *
+ * UsbMscDriver usbMsc;
+ * FsVolume key;
+ * ...
+ *
+ *   // Init USB MSC driver.
+ *   if (!usbMsc.begin()) {
+ *     ... handle driver init failure.
+ *   }
+ *   // Init FAT/exFAT volume.
+ *   if (!key.begin(&usbMsc)) {
+ *     ... handle FAT/exFAT failure.
+ *   }
+ */
+#ifndef USE_BLOCK_DEVICE_INTERFACE
+#define USE_BLOCK_DEVICE_INTERFACE 0
+#endif  // USE_BLOCK_DEVICE_INTERFACE
+ /**
+ * SD_CHIP_SELECT_MODE defines how the functions
+ * void sdCsInit(SdCsPin_t pin) {pinMode(pin, OUTPUT);}
+ * and
+ * void sdCsWrite(SdCsPin_t pin, bool level) {digitalWrite(pin, level);}
+ * are defined.
+ *
+ * 0 - Internal definition is a strong symbol and can't be replaced.
+ *
+ * 1 - Internal definition is a weak symbol and can be replaced.
+ *
+ * 2 - No internal definition and must be defined in the application.
+ */
+#ifndef SD_CHIP_SELECT_MODE
+#define SD_CHIP_SELECT_MODE 0
+#endif  // SD_CHIP_SELECT_MODE
+/** Type for card chip select pin. */
+typedef uint8_t SdCsPin_t;
+//------------------------------------------------------------------------------
+/**
+ * Set USE_LONG_FILE_NAMES nonzero to use long file names (LFN) in FAT16/FAT32.
+ * exFAT always uses long file names.
+ *
+ * Long File Name are limited to a maximum length of 255 characters.
+ *
+ * This implementation allows 7-bit characters in the range
+ * 0X20 to 0X7E except the following characters are not allowed:
+ *
+ *  < (less than)
+ *  > (greater than)
+ *  : (colon)
+ *  " (double quote)
+ *  / (forward slash)
+ *  \ (backslash)
+ *  | (vertical bar or pipe)
+ *  ? (question mark)
+ *  * (asterisk)
+ *
+ */
+#ifndef USE_LONG_FILE_NAMES
+#define USE_LONG_FILE_NAMES 1
+#endif  // USE_LONG_FILE_NAMES
+/**
+ * Set USE_UTF8_LONG_NAMES nonzero to use UTF-8 file names. Use of UTF-8 names
+ * will require significantly more flash memory and a small amount of extra
+ * RAM.
+ *
+ * UTF-8 filenames allow encoding of 1,112,064 code points in Unicode using
+ * one to four one-byte (8-bit) code units.
+ *
+ * As of Version 13.0, the Unicode Standard defines 143,859 characters.
+ *
+ * getName() will return UTF-8 strings and printName() will write UTF-8 strings.
+ */
+#ifndef USE_UTF8_LONG_NAMES
+#define USE_UTF8_LONG_NAMES 0
+#endif  // USE_UTF8_LONG_NAMES
+
+#if USE_UTF8_LONG_NAMES && !USE_LONG_FILE_NAMES
+#error "USE_UTF8_LONG_NAMES requires USE_LONG_FILE_NAMES to be non-zero."
+#endif  // USE_UTF8_LONG_NAMES && !USE_LONG_FILE_NAMES
+//------------------------------------------------------------------------------
+/**
+ * Set MAINTAIN_FREE_CLUSTER_COUNT nonzero to keep the count of free clusters
+ * updated.  This will increase the speed of the freeClusterCount() call
+ * after the first call.  Extra flash will be required.
+ */
+#ifndef MAINTAIN_FREE_CLUSTER_COUNT
+#define MAINTAIN_FREE_CLUSTER_COUNT 0
+#endif  // MAINTAIN_FREE_CLUSTER_COUNT
+//------------------------------------------------------------------------------
+/**
+ * Set the default file time stamp when a RTC callback is not used.
+ * A valid date and time is required by the FAT/exFAT standard.
+ *
+ * The default below is YYYY-01-01 00:00:00 midnight where YYYY is
+ * the compile year from the __DATE__ macro.  This is easy to recognize
+ * as a placeholder for a correct date/time.
+ *
+ * The full compile date is:
+ * FS_DATE(compileYear(), compileMonth(), compileDay())
+ *
+ * The full compile time is:
+ * FS_TIME(compileHour(), compileMinute(), compileSecond())
+ */
+#define FS_DEFAULT_DATE FS_DATE(compileYear(), 1, 1)
+/** 00:00:00 midnight */
+#define FS_DEFAULT_TIME FS_TIME(0, 0, 0)
+//------------------------------------------------------------------------------
+/**
+ * If CHECK_FLASH_PROGRAMMING is zero, overlap of single sector flash
+ * programming and other operations will be allowed for faster write
+ * performance.
+ *
+ * Some cards will not sleep in low power mode unless CHECK_FLASH_PROGRAMMING
+ * is non-zero.
+ */
+#ifndef CHECK_FLASH_PROGRAMMING
+#define CHECK_FLASH_PROGRAMMING 1
+#endif  // CHECK_FLASH_PROGRAMMING
+//------------------------------------------------------------------------------
+/**
+ * To enable SD card CRC checking for SPI, set USE_SD_CRC nonzero.
+ *
+ * Set USE_SD_CRC to 1 to use a smaller CRC-CCITT function.  This function
+ * is slower for AVR but may be fast for ARM and other processors.
+ *
+ * Set USE_SD_CRC to 2 to used a larger table driven CRC-CCITT function.  This
+ * function is faster for AVR but may be slower for ARM and other processors.
+ */
+#ifndef USE_SD_CRC
+#define USE_SD_CRC 0
+#endif  // USE_SD_CRC
+//------------------------------------------------------------------------------
+/** If the symbol USE_FCNTL_H is nonzero, open flags for access modes O_RDONLY,
+ * O_WRONLY, O_RDWR and the open modifiers O_APPEND, O_CREAT, O_EXCL, O_SYNC
+ * will be defined by including the system file fcntl.h.
+ */
+#ifndef USE_FCNTL_H
+#if defined(__AVR__)
+// AVR fcntl.h does not define open flags.
+#define USE_FCNTL_H 0
+#elif defined(PLATFORM_ID)
+// Particle boards - use fcntl.h.
+#define USE_FCNTL_H 1
+#elif defined(__arm__)
+// ARM gcc defines open flags.
+#define USE_FCNTL_H 1
+#elif defined(ESP32)
+#define USE_FCNTL_H 1
+#else  // defined(__AVR__)
+#define USE_FCNTL_H 0
+#endif  // defined(__AVR__)
+#endif  // USE_FCNTL_H
+//------------------------------------------------------------------------------
+/**
+ * Set INCLUDE_SDIOS nonzero to include sdios.h in SdFat.h.
+ * sdios.h provides C++ style IO Streams.
+ */
+#ifndef INCLUDE_SDIOS
+#define INCLUDE_SDIOS 0
+#endif  // INCLUDE_SDIOS
+//------------------------------------------------------------------------------
+/**
+ * Set FAT12_SUPPORT nonzero to enable use if FAT12 volumes.
+ * FAT12 has not been well tested and requires additional flash.
+ */
+#ifndef FAT12_SUPPORT
+#define FAT12_SUPPORT 0
+#endif  // FAT12_SUPPORT
+//------------------------------------------------------------------------------
+/**
+ * Set DESTRUCTOR_CLOSES_FILE nonzero to close a file in its destructor.
+ *
+ * Causes use of lots of heap in ARM.
+ */
+#ifndef DESTRUCTOR_CLOSES_FILE
+#define DESTRUCTOR_CLOSES_FILE 0
+#endif  // DESTRUCTOR_CLOSES_FILE
+//------------------------------------------------------------------------------
+/**
+ * Call flush for endl if ENDL_CALLS_FLUSH is nonzero
+ *
+ * The standard for iostreams is to call flush.  This is very costly for
+ * SdFat.  Each call to flush causes 2048 bytes of I/O to the SD.
+ *
+ * SdFat has a single 512 byte buffer for SD I/O so it must write the current
+ * data sector to the SD, read the directory sector from the SD, update the
+ * directory entry, write the directory sector to the SD and read the data
+ * sector back into the buffer.
+ *
+ * The SD flash memory controller is not designed for this many rewrites
+ * so performance may be reduced by more than a factor of 100.
+ *
+ * If ENDL_CALLS_FLUSH is zero, you must call flush and/or close to force
+ * all data to be written to the SD.
+ */
+#ifndef ENDL_CALLS_FLUSH
+#define ENDL_CALLS_FLUSH 0
+#endif  // ENDL_CALLS_FLUSH
+//------------------------------------------------------------------------------
+/**
+ * Set USE_SIMPLE_LITTLE_ENDIAN nonzero for little endian processors
+ * with no memory alignment restrictions.
+ */
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__\
+  && (defined(__AVR__) || defined(__ARM_FEATURE_UNALIGNED))
+#define USE_SIMPLE_LITTLE_ENDIAN 1
+#else  // __BYTE_ORDER_
+#define USE_SIMPLE_LITTLE_ENDIAN 0
+#endif  // __BYTE_ORDER_
+//------------------------------------------------------------------------------
+/**
+ * Set USE_SEPARATE_FAT_CACHE nonzero to use a second 512 byte cache
+ * for FAT16/FAT32 table entries.  This improves performance for large
+ * writes that are not a multiple of 512 bytes.
+ */
+#ifdef __arm__
+#define USE_SEPARATE_FAT_CACHE 1
+#else  // __arm__
+#define USE_SEPARATE_FAT_CACHE 0
+#endif  // __arm__
+//------------------------------------------------------------------------------
+/**
+ * Set USE_EXFAT_BITMAP_CACHE nonzero to use a second 512 byte cache
+ * for exFAT bitmap entries.  This improves performance for large
+ * writes that are not a multiple of 512 bytes.
+ */
+#ifdef __arm__
+#define USE_EXFAT_BITMAP_CACHE 1
+#else  // __arm__
+#define USE_EXFAT_BITMAP_CACHE 0
+#endif  // __arm__
+//------------------------------------------------------------------------------
+/**
+ * Set USE_MULTI_SECTOR_IO nonzero to use multi-sector SD read/write.
+ *
+ * Don't use mult-sector read/write on small AVR boards.
+ */
+#if defined(RAMEND) && RAMEND < 3000
+#define USE_MULTI_SECTOR_IO 0
+#else  // RAMEND
+#define USE_MULTI_SECTOR_IO 1
+#endif  // RAMEND
+//------------------------------------------------------------------------------
+/** Enable SDIO driver if available. */
+#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
+// Pseudo pin select for SDIO.
+#ifndef BUILTIN_SDCARD
+#define BUILTIN_SDCARD 254
+#endif  // BUILTIN_SDCARD
+// SPI for built-in card.
+#ifndef SDCARD_SPI
+#define SDCARD_SPI      SPI1
+#define SDCARD_MISO_PIN 59
+#define SDCARD_MOSI_PIN 61
+#define SDCARD_SCK_PIN  60
+#define SDCARD_SS_PIN   62
+#endif  // SDCARD_SPI
+#define HAS_SDIO_CLASS 1
+#endif  // defined(__MK64FX512__) || defined(__MK66FX1M0__)
+#if defined(__IMXRT1062__)
+#define HAS_SDIO_CLASS 1
+#endif  // defined(__IMXRT1062__)
+//------------------------------------------------------------------------------
+/**
+ * Determine the default SPI configuration.
+ */
+#if defined(ARDUINO_ARCH_APOLLO3)\
+  || (defined(__AVR__) && defined(SPDR) && defined(SPSR) && defined(SPIF))\
+  || (defined(__AVR__) && defined(SPI0) && defined(SPI_RXCIF_bm))\
+  || defined(ESP8266) || defined(ESP32)\
+  || defined(PLATFORM_ID)\
+  || defined(ARDUINO_SAM_DUE)\
+  || defined(STM32_CORE_VERSION)\
+  || defined(__STM32F1__) || defined(__STM32F4__)\
+  || (defined(CORE_TEENSY) && defined(__arm__))
+#define SD_HAS_CUSTOM_SPI 1
+#else  // SD_HAS_CUSTOM_SPI
+// Use standard SPI library.
+#define SD_HAS_CUSTOM_SPI 0
+#endif  // SD_HAS_CUSTOM_SPI
+//------------------------------------------------------------------------------
+#ifndef HAS_SDIO_CLASS
+/** Default is no SDIO. */
+#define HAS_SDIO_CLASS 0
+#endif  // HAS_SDIO_CLASS
+
+#endif  // SdFatConfig_h

+ 96 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiArduinoDriver.h

@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+/**
+ * \file
+ * \brief SpiDriver classes for Arduino compatible systems.
+ */
+#ifndef SdSpiArduinoDriver_h
+#define SdSpiArduinoDriver_h
+//==============================================================================
+#if SPI_DRIVER_SELECT == 0 && SD_HAS_CUSTOM_SPI
+#define SD_USE_CUSTOM_SPI
+#endif  // SPI_DRIVER_SELECT == 0 && SD_HAS_CUSTOM_SPI
+/**
+ * \class SdSpiArduinoDriver
+ * \brief Optimized SPI class for access to SD and SDHC flash memory cards.
+ */
+class SdSpiArduinoDriver {
+ public:
+  /** Activate SPI hardware. */
+  void activate();
+  /** Initialize the SPI bus.
+   *
+   * \param[in] spiConfig SD card configuration.
+   */
+  void begin(SdSpiConfig spiConfig);
+  /** Deactivate SPI hardware. */
+  void deactivate();
+  /** End use of SPI driver after begin() call. */
+  void end();
+  /** Receive a byte.
+   *
+   * \return The byte.
+   */
+  uint8_t receive();
+  /** Receive multiple bytes.
+  *
+  * \param[out] buf Buffer to receive the data.
+  * \param[in] count Number of bytes to receive.
+  *
+  * \return Zero for no error or nonzero error code.
+  */
+  uint8_t receive(uint8_t* buf, size_t count);
+  /** Send a byte.
+   *
+   * \param[in] data Byte to send
+   */
+  void send(uint8_t data);
+  /** Send multiple bytes.
+   *
+   * \param[in] buf Buffer for data to be sent.
+   * \param[in] count Number of bytes to send.
+   */
+  void send(const uint8_t* buf, size_t count);
+  /** Save high speed SPISettings after SD initialization.
+   *
+   * \param[in] maxSck Maximum SCK frequency.
+   */
+  void setSckSpeed(uint32_t maxSck) {
+    m_spiSettings = SPISettings(maxSck, MSBFIRST, SPI_MODE0);
+  }
+
+ private:
+  SPIClass *m_spi;
+  SPISettings m_spiSettings;
+};
+/** Typedef for use of SdSpiArduinoDriver */
+typedef SdSpiArduinoDriver SdSpiDriver;
+//------------------------------------------------------------------------------
+#ifndef SD_USE_CUSTOM_SPI
+#include "SdSpiLibDriver.h"
+#elif defined(__AVR__)
+#include "SdSpiAvr.h"
+#endif  // __AVR__
+#endif  // SdSpiArduinoDriver_h

+ 79 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiArtemis.cpp

@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "SdSpiDriver.h"
+#if defined(SD_USE_CUSTOM_SPI) && defined(ARDUINO_ARCH_APOLLO3)
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::activate() {
+  m_spi->beginTransaction(m_spiSettings);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) {
+  if (spiConfig.spiPort) {
+    m_spi = spiConfig.spiPort;
+  } else {
+    m_spi = &SPI;
+  }
+  m_spi->begin();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::deactivate() {
+  m_spi->endTransaction();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::end() {
+  m_spi->end();
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive() {
+  return m_spi->transfer(0XFF);
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) {
+  memset(buf, 0XFF, count);
+  m_spi->transfer(buf, count);
+  return 0;
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(uint8_t data) {
+  m_spi->transfer(data);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(const uint8_t* buf, size_t count) {
+  // If not a multiple of four.  Command with CRC used six byte send.
+  while (count%4) {
+    send(*buf++);
+    count--;
+  }
+  // Convert byte array to 4 byte array.
+  uint32_t myArray[count/4]; // NOLINT
+  for (int x = 0; x < count/4; x++) {
+    myArray[x] = ((uint32_t)buf[(x * 4) + 3] << (8 * 3)) |
+                 ((uint32_t)buf[(x * 4) + 2] << (8 * 2)) |
+                 ((uint32_t)buf[(x * 4) + 1] << (8 * 1)) |
+                 ((uint32_t)buf[(x * 4) + 0] << (8 * 0));
+  }
+  m_spi->transfer(reinterpret_cast<void *>(myArray), count);
+}
+#endif  // defined(SD_USE_CUSTOM_SPI) && defined(ARDUINO_ARCH_APOLLO3)

+ 124 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiAvr.h

@@ -0,0 +1,124 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef SdSpiAvr_h
+#define SdSpiAvr_h
+// Use of in-line for AVR to save flash.
+#define nop asm volatile ("nop\n\t")
+//------------------------------------------------------------------------------
+inline void SdSpiArduinoDriver::activate() {
+  SPI.beginTransaction(m_spiSettings);
+}
+//------------------------------------------------------------------------------
+inline void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) {
+  (void)spiConfig;
+  SPI.begin();
+}
+//------------------------------------------------------------------------------
+inline void SdSpiArduinoDriver::deactivate() {
+  SPI.endTransaction();
+}
+//------------------------------------------------------------------------------
+inline void SdSpiArduinoDriver::end() {
+  SPI.end();
+}
+//------------------------------------------------------------------------------
+inline uint8_t SdSpiArduinoDriver::receive() {
+  return SPI.transfer(0XFF);
+}
+//------------------------------------------------------------------------------
+inline uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) {
+  if (count == 0) {
+    return 0;
+  }
+#ifdef SPSR
+  SPDR = 0XFF;
+  while (--count) {
+    // nops optimize loop for 16MHz CPU 8 MHz SPI
+    nop;
+    nop;
+    while (!(SPSR & _BV(SPIF))) {}
+    uint8_t in = SPDR;
+    SPDR = 0XFF;
+    *buf++ = in;
+  }
+  while (!(SPSR & _BV(SPIF))) {}
+  *buf = SPDR;
+#elif defined(SPI_RXCIF_bm)
+  SPI0.DATA = 0XFF;
+  while (--count) {
+    // nops optimize loop for ATmega4809 16MHz CPU 8 MHz SPI
+    nop;
+    nop;
+    nop;
+    nop;
+    while (!(SPI0.INTFLAGS & SPI_RXCIF_bm)) {}
+    uint8_t in = SPI0.DATA;
+    SPI0.DATA = 0XFF;
+    *buf++ = in;
+  }
+  while (!(SPI0.INTFLAGS & SPI_RXCIF_bm)) {}
+  *buf = SPI0.DATA;
+#else  // SPSR
+#error Unsupported AVR CPU - edit SdFatConfig.h to use standard SPI library.
+#endif  // SPSR
+  return 0;
+}
+//------------------------------------------------------------------------------
+inline void SdSpiArduinoDriver::send(uint8_t data) {
+  SPI.transfer(data);
+}
+//------------------------------------------------------------------------------
+inline void SdSpiArduinoDriver::send(const uint8_t* buf , size_t count) {
+  if (count == 0) {
+    return;
+  }
+#ifdef SPSR
+  SPDR = *buf++;
+  while (--count) {
+    uint8_t b = *buf++;
+    // nops optimize loop for 16MHz CPU 8 MHz SPI
+    nop;
+    nop;
+    while (!(SPSR & (1 << SPIF))) {}
+    SPDR = b;
+  }
+  while (!(SPSR & (1 << SPIF))) {}
+#elif defined(SPI_RXCIF_bm)
+  SPI0.DATA = *buf++;
+  while (--count) {
+    uint8_t b = *buf++;
+    // nops optimize loop for ATmega4809 16MHz CPU 8 MHz SPI
+    nop;
+    nop;
+    nop;
+    while (!(SPI0.INTFLAGS & SPI_RXCIF_bm)) {}
+    SPI0.DATA = b;
+  }
+  while (!(SPI0.INTFLAGS & SPI_RXCIF_bm)) {}
+#else  // SPSR
+#error Unsupported AVR CPU - edit SdFatConfig.h to use standard SPI library.
+#endif  // SPSR
+}
+#endif  // SdSpiAvr_h

+ 200 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiBareUnoDriver.h

@@ -0,0 +1,200 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef SdSpiBareUnoDriver_h
+#define SdSpiBareUnoDriver_h
+/**
+ * \file
+ * \brief Driver to test with no Arduino includes.
+ */
+
+#include <avr/interrupt.h>
+#include "../common/SysCall.h"
+#define nop asm volatile ("nop\n\t")
+#ifndef HIGH
+#define HIGH 1
+#endif  // HIGH
+#ifndef LOW
+#define LOW 0
+#endif  // LOW
+#ifndef INPUT
+#define INPUT 0
+#endif  // INPUT
+#ifndef OUTPUT
+#define OUTPUT 1
+#endif  // OUTPUT
+
+inline uint8_t unoBit(uint8_t pin) {
+  return 1 << (pin < 8 ? pin : pin < 14 ? pin - 8 : pin - 14);
+}
+inline uint8_t unoDigitalRead(uint8_t pin) {
+  volatile uint8_t* reg = pin < 8 ? &PIND : pin < 14 ? &PINB : &PINC;
+  return *reg & unoBit(pin);
+}
+inline void unoDigitalWrite(uint8_t pin, uint8_t value) {
+  volatile uint8_t* port = pin < 8 ? &PORTD : pin < 14 ? &PORTB : &PORTC;
+  uint8_t bit = unoBit(pin);
+  cli();
+  if (value) {
+    *port |= bit;
+  } else {
+    *port &= ~bit;
+  }
+  sei();
+}
+
+inline void unoPinMode(uint8_t pin, uint8_t mode) {
+  uint8_t bit = unoBit(pin);
+  volatile uint8_t* reg = pin < 8 ? &DDRD : pin < 14 ? &DDRB : &DDRC;
+
+  cli();
+  if (mode == OUTPUT) {
+    *reg |= bit;
+  } else {
+    *reg &= ~bit;
+    // handle INPUT pull-up
+    unoDigitalWrite(pin, mode != INPUT);
+  }
+  sei();
+}
+
+#define UNO_SS   10
+#define UNO_MOSI 11
+#define UNO_MISO 12
+#define UNO_SCK  13
+//------------------------------------------------------------------------------
+/**
+ * \class SdSpiDriverBareUno
+ * \brief Optimized SPI class for access to SD and SDHC flash memory cards.
+ */
+class SdSpiDriverBareUno {
+ public:
+  /** Activate SPI hardware. */
+  void activate() {}
+  /** Initialize the SPI bus.
+   *
+   * \param[in] spiConfig SD card configuration.
+   */
+  void begin(SdSpiConfig spiConfig) {
+    m_csPin = spiConfig.csPin;
+    unoPinMode(m_csPin, OUTPUT);
+    unoDigitalWrite(m_csPin, HIGH);
+    unoDigitalWrite(UNO_SS, HIGH);
+    unoPinMode(UNO_SS, OUTPUT);
+    SPCR |= _BV(MSTR);
+    SPCR |= _BV(SPE);
+    SPSR = 0;
+    unoPinMode(UNO_SCK, OUTPUT);
+    unoPinMode(UNO_MOSI, OUTPUT);
+  }
+  /** Deactivate SPI hardware. */
+  void deactivate() {}
+  /** deactivate SPI driver. */
+  void end() {}
+  /** Receive a byte.
+   *
+   * \return The byte.
+   */
+  uint8_t receive() {
+    return transfer(0XFF);
+  }
+  /** Receive multiple bytes.
+  *
+  * \param[out] buf Buffer to receive the data.
+  * \param[in] count Number of bytes to receive.
+  *
+  * \return Zero for no error or nonzero error code.
+  */
+  uint8_t receive(uint8_t* buf, size_t count) {
+    if (count == 0) {
+      return 0;
+    }
+    uint8_t* pr = buf;
+    SPDR = 0XFF;
+    while (--count > 0) {
+      while (!(SPSR & _BV(SPIF))) {}
+      uint8_t in = SPDR;
+      SPDR = 0XFF;
+      *pr++ = in;
+      // nops to optimize loop for 16MHz CPU 8 MHz SPI
+      nop;
+      nop;
+    }
+    while (!(SPSR & _BV(SPIF))) {}
+    *pr = SPDR;
+    return 0;
+  }
+  /** Send a byte.
+   *
+   * \param[in] data Byte to send
+   */
+  void send(uint8_t data) {
+    transfer(data);
+  }
+  /** Send multiple bytes.
+   *
+   * \param[in] buf Buffer for data to be sent.
+   * \param[in] count Number of bytes to send.
+   */
+  void send(const uint8_t* buf, size_t count) {
+    if (count == 0) {
+      return;
+    }
+    SPDR = *buf++;
+    while (--count > 0) {
+      uint8_t b = *buf++;
+      while (!(SPSR & (1 << SPIF))) {}
+      SPDR = b;
+      // nops to optimize loop for 16MHz CPU 8 MHz SPI
+      nop;
+      nop;
+    }
+    while (!(SPSR & (1 << SPIF))) {}
+  }
+  /** Set CS low. */
+  void select() {
+     unoDigitalWrite(m_csPin, LOW);
+  }
+  /** Save high speed SPISettings after SD initialization.
+   *
+   * \param[in] spiConfig SPI options.
+   */
+  void setSckSpeed(uint32_t maxSck) {
+    (void)maxSck;
+    SPSR |= 1 << SPI2X;
+  }
+  static uint8_t transfer(uint8_t data) {
+    SPDR = data;
+    while (!(SPSR & _BV(SPIF))) {}  // wait
+    return SPDR;
+  }
+  /** Set CS high. */
+  void unselect() {
+    unoDigitalWrite(m_csPin, HIGH);
+  }
+
+ private:
+  SdCsPin_t m_csPin;
+};
+#endif  // SdSpiBareUnoDriver_h

+ 78 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiBaseClass.h

@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+/**
+ * \file
+ * \brief Base class for external SPI driver.
+ */
+#ifndef SdSpiBaseClass_h
+#define SdSpiBaseClass_h
+/**
+ * \class SdSpiBaseClass
+ * \brief Base class for external SPI drivers
+ */
+class SdSpiBaseClass {
+ public:
+  /** Activate SPI hardware. */
+  virtual void activate() {}
+  /** Initialize the SPI bus.
+   *
+   * \param[in] config SPI configuration.
+   */
+  virtual void begin(SdSpiConfig config) = 0;
+  /** Deactivate SPI hardware. */
+  virtual void deactivate() {}
+  /** deactivate SPI driver. */
+  virtual void end() {}
+  /** Receive a byte.
+   *
+   * \return The byte.
+   */
+  virtual uint8_t receive() = 0;
+  /** Receive multiple bytes.
+  *
+  * \param[out] buf Buffer to receive the data.
+  * \param[in] count Number of bytes to receive.
+  *
+  * \return Zero for no error or nonzero error code.
+  */
+  virtual uint8_t receive(uint8_t* buf, size_t count) = 0;
+  /** Send a byte.
+   *
+   * \param[in] data Byte to send
+   */
+  virtual void send(uint8_t data) = 0;
+  /** Send multiple bytes.
+   *
+   * \param[in] buf Buffer for data to be sent.
+   * \param[in] count Number of bytes to send.
+   */
+  virtual void send(const uint8_t* buf, size_t count) = 0;
+  /** Save high speed SPISettings after SD initialization.
+   *
+   * \param[in] maxSck Maximum SCK frequency.
+   */
+  virtual void setSckSpeed(uint32_t maxSck) {(void)maxSck;}
+};
+#endif  // SdSpiBaseClass_h

+ 48 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiChipSelect.cpp

@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "SdSpiDriver.h"
+#if ENABLE_ARDUINO_FEATURES
+#if SD_CHIP_SELECT_MODE == 0
+//------------------------------------------------------------------------------
+void sdCsInit(SdCsPin_t pin) {
+  pinMode(pin, OUTPUT);
+}
+//------------------------------------------------------------------------------
+void sdCsWrite(SdCsPin_t pin, bool level) {
+  digitalWrite(pin, level);
+}
+#elif SD_CHIP_SELECT_MODE == 1
+//------------------------------------------------------------------------------
+__attribute__((weak))
+void sdCsInit(SdCsPin_t pin) {
+  pinMode(pin, OUTPUT);
+}
+//------------------------------------------------------------------------------
+__attribute__((weak))
+void sdCsWrite(SdCsPin_t pin, bool level) {
+  digitalWrite(pin, level);
+}
+#endif  // SD_CHIP_SELECT_MODE == 0
+#endif  // ENABLE_ARDUINO_FEATURES

+ 155 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiDriver.h

@@ -0,0 +1,155 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+/**
+ * \file
+ * \brief SpiDriver classes
+ */
+#ifndef SdSpiDriver_h
+#define SdSpiDriver_h
+#include "../common/SysCall.h"
+/**
+ * Initialize SD chip select pin.
+ *
+ * \param[in] pin SD card chip select pin.
+ */
+void sdCsInit(SdCsPin_t pin);
+/**
+ * Initialize SD chip select pin.
+ *
+ * \param[in] pin SD card chip select pin.
+ * \param[in] level SD card chip select level.
+ */
+void sdCsWrite(SdCsPin_t pin, bool level);
+//------------------------------------------------------------------------------
+/** SPI bus is share with other devices. */
+const uint8_t SHARED_SPI = 0;
+#if ENABLE_DEDICATED_SPI
+/** The SD is the only device on the SPI bus. */
+const uint8_t DEDICATED_SPI = 1;
+/**
+ * \param[in] opt option field of SdSpiConfig.
+ * \return true for dedicated SPI.
+ */
+inline bool spiOptionDedicated(uint8_t opt) {return opt & DEDICATED_SPI;}
+#else  // ENABLE_DEDICATED_SPI
+/**
+ * \param[in] opt option field of SdSpiConfig.
+ * \return true for dedicated SPI.
+ */
+inline bool spiOptionDedicated(uint8_t opt) {(void)opt; return false;}
+#endif  // ENABLE_DEDICATED_SPI
+//------------------------------------------------------------------------------
+/** SPISettings for SCK frequency in Hz. */
+#define SD_SCK_HZ(maxSpeed) (maxSpeed)
+/** SPISettings for SCK frequency in MHz. */
+#define SD_SCK_MHZ(maxMhz) (1000000UL*(maxMhz))
+// SPI divisor constants - obsolete.
+/** Set SCK to max rate. */
+#define SPI_FULL_SPEED SD_SCK_MHZ(50)
+/** Set SCK rate to 16 MHz for Due */
+#define SPI_DIV3_SPEED SD_SCK_MHZ(16)
+/** Set SCK rate to 4 MHz for AVR. */
+#define SPI_HALF_SPEED SD_SCK_MHZ(4)
+/** Set SCK rate to 8 MHz for Due */
+#define SPI_DIV6_SPEED SD_SCK_MHZ(8)
+/** Set SCK rate to 2 MHz for AVR. */
+#define SPI_QUARTER_SPEED SD_SCK_MHZ(2)
+/** Set SCK rate to 1 MHz for AVR. */
+#define SPI_EIGHTH_SPEED SD_SCK_MHZ(1)
+/** Set SCK rate to 500 kHz for AVR. */
+#define SPI_SIXTEENTH_SPEED SD_SCK_HZ(500000)
+//------------------------------------------------------------------------------
+#if SPI_DRIVER_SELECT < 2
+#include "SPI.h"
+/** Port type for Arduino SPI hardware driver. */
+typedef SPIClass SpiPort_t;
+#elif SPI_DRIVER_SELECT == 2
+class SdSpiSoftDriver;
+/** Port type for software SPI driver. */
+typedef SdSpiSoftDriver SpiPort_t;
+#elif SPI_DRIVER_SELECT == 3
+class SdSpiBaseClass;
+/** Port type for extrernal SPI driver. */
+typedef SdSpiBaseClass SpiPort_t;
+#else  // SPI_DRIVER_SELECT
+typedef void*  SpiPort_t;
+#endif  // SPI_DRIVER_SELECT
+//------------------------------------------------------------------------------
+/**
+ * \class SdSpiConfig
+ * \brief SPI card configuration.
+ */
+class SdSpiConfig {
+ public:
+   /** SdSpiConfig constructor.
+   *
+   * \param[in] cs Chip select pin.
+   * \param[in] opt Options.
+   * \param[in] maxSpeed Maximum SCK frequency.
+   * \param[in] port The SPI port to use.
+   */
+  SdSpiConfig(SdCsPin_t cs, uint8_t opt, uint32_t maxSpeed, SpiPort_t* port) :
+    csPin(cs), options(opt), maxSck(maxSpeed), spiPort(port) {}
+
+  /** SdSpiConfig constructor.
+   *
+   * \param[in] cs Chip select pin.
+   * \param[in] opt Options.
+   * \param[in] maxSpeed Maximum SCK frequency.
+   */
+  SdSpiConfig(SdCsPin_t cs, uint8_t opt, uint32_t maxSpeed) :
+    csPin(cs), options(opt), maxSck(maxSpeed) {}
+  /** SdSpiConfig constructor.
+   *
+   * \param[in] cs Chip select pin.
+   * \param[in] opt Options.
+   */
+  SdSpiConfig(SdCsPin_t cs, uint8_t opt) : csPin(cs), options(opt) {}
+  /** SdSpiConfig constructor.
+   *
+   * \param[in] cs Chip select pin.
+   */
+  explicit SdSpiConfig(SdCsPin_t cs) : csPin(cs) {}
+
+  /** Chip select pin. */
+  const SdCsPin_t csPin;
+  /** Options */
+  const uint8_t options = SHARED_SPI;
+  /** Max SCK frequency */
+  const uint32_t maxSck = SD_SCK_MHZ(50);
+  /** SPI port */
+  SpiPort_t* spiPort = nullptr;
+};
+#if SPI_DRIVER_SELECT < 2
+#include "SdSpiArduinoDriver.h"
+#elif SPI_DRIVER_SELECT == 2
+#include "SdSpiSoftDriver.h"
+#elif SPI_DRIVER_SELECT == 3
+#include "SdSpiBaseClass.h"
+typedef SdSpiBaseClass SdSpiDriver;
+#else  // SPI_DRIVER_SELECT
+#error Invalid SPI_DRIVER_SELECT
+#endif  // SPI_DRIVER_SELECT
+#endif  // SdSpiDriver_h

+ 216 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiDue.cpp

@@ -0,0 +1,216 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "SdSpiDriver.h"
+#if defined(SD_USE_CUSTOM_SPI) && defined(ARDUINO_SAM_DUE)
+/* Use SAM3X DMAC if nonzero */
+#define USE_SAM3X_DMAC 1
+/* Use extra Bus Matrix arbitration fix if nonzero */
+#define USE_SAM3X_BUS_MATRIX_FIX 1
+/* Time in ms for DMA receive timeout */
+#define SAM3X_DMA_TIMEOUT 100
+/* chip select register number */
+#define SPI_CHIP_SEL 3
+/* DMAC receive channel */
+#define SPI_DMAC_RX_CH 1
+/* DMAC transmit channel */
+#define SPI_DMAC_TX_CH 0
+/* DMAC Channel HW Interface Number for SPI TX. */
+#define SPI_TX_IDX 1
+/* DMAC Channel HW Interface Number for SPI RX. */
+#define SPI_RX_IDX 2
+//------------------------------------------------------------------------------
+/* Disable DMA Controller. */
+static void dmac_disable() {
+  DMAC->DMAC_EN &= (~DMAC_EN_ENABLE);
+}
+/* Enable DMA Controller. */
+static void dmac_enable() {
+  DMAC->DMAC_EN = DMAC_EN_ENABLE;
+}
+/* Disable DMA Channel. */
+static void dmac_channel_disable(uint32_t ul_num) {
+  DMAC->DMAC_CHDR = DMAC_CHDR_DIS0 << ul_num;
+}
+/* Enable DMA Channel. */
+static void dmac_channel_enable(uint32_t ul_num) {
+  DMAC->DMAC_CHER = DMAC_CHER_ENA0 << ul_num;
+}
+/* Poll for transfer complete. */
+static bool dmac_channel_transfer_done(uint32_t ul_num) {
+  return (DMAC->DMAC_CHSR & (DMAC_CHSR_ENA0 << ul_num)) ? false : true;
+}
+//------------------------------------------------------------------------------
+// start RX DMA
+static void spiDmaRX(uint8_t* dst, uint16_t count) {
+  dmac_channel_disable(SPI_DMAC_RX_CH);
+  DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_SADDR = (uint32_t)&SPI0->SPI_RDR;
+  DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_DADDR = (uint32_t)dst;
+  DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_DSCR =  0;
+  DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CTRLA = count |
+      DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE;
+  DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CTRLB = DMAC_CTRLB_SRC_DSCR |
+      DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_PER2MEM_DMA_FC |
+      DMAC_CTRLB_SRC_INCR_FIXED | DMAC_CTRLB_DST_INCR_INCREMENTING;
+  DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CFG = DMAC_CFG_SRC_PER(SPI_RX_IDX) |
+      DMAC_CFG_SRC_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG;
+  dmac_channel_enable(SPI_DMAC_RX_CH);
+}
+//------------------------------------------------------------------------------
+// start TX DMA
+static void spiDmaTX(const uint8_t* src, uint16_t count) {
+  static uint8_t ff = 0XFF;
+  uint32_t src_incr = DMAC_CTRLB_SRC_INCR_INCREMENTING;
+  if (!src) {
+    src = &ff;
+    src_incr = DMAC_CTRLB_SRC_INCR_FIXED;
+  }
+  dmac_channel_disable(SPI_DMAC_TX_CH);
+  DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_SADDR = (uint32_t)src;
+  DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DADDR = (uint32_t)&SPI0->SPI_TDR;
+  DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DSCR =  0;
+  DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLA = count |
+      DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE;
+
+  DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLB =  DMAC_CTRLB_SRC_DSCR |
+      DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC |
+      src_incr | DMAC_CTRLB_DST_INCR_FIXED;
+
+  DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CFG = DMAC_CFG_DST_PER(SPI_TX_IDX) |
+      DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ALAP_CFG;
+
+  dmac_channel_enable(SPI_DMAC_TX_CH);
+}
+//------------------------------------------------------------------------------
+//  initialize SPI controller
+void SdSpiArduinoDriver::activate() {
+  SPI.beginTransaction(m_spiSettings);
+
+  Spi* pSpi = SPI0;
+  // Save the divisor
+  uint32_t scbr = pSpi->SPI_CSR[SPI_CHIP_SEL] & 0XFF00;
+  // Disable SPI
+  pSpi->SPI_CR = SPI_CR_SPIDIS;
+  // reset SPI
+  pSpi->SPI_CR = SPI_CR_SWRST;
+  // no mode fault detection, set master mode
+  pSpi->SPI_MR = SPI_PCS(SPI_CHIP_SEL) | SPI_MR_MODFDIS | SPI_MR_MSTR;
+  // mode 0, 8-bit,
+  pSpi->SPI_CSR[SPI_CHIP_SEL] = scbr | SPI_CSR_CSAAT | SPI_CSR_NCPHA;
+  // enable SPI
+  pSpi->SPI_CR |= SPI_CR_SPIEN;
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) {
+  (void)spiConfig;
+  SPI.begin();
+#if USE_SAM3X_DMAC
+  pmc_enable_periph_clk(ID_DMAC);
+  dmac_disable();
+  DMAC->DMAC_GCFG = DMAC_GCFG_ARB_CFG_FIXED;
+  dmac_enable();
+#if USE_SAM3X_BUS_MATRIX_FIX
+  MATRIX->MATRIX_WPMR = 0x4d415400;
+  MATRIX->MATRIX_MCFG[1] = 1;
+  MATRIX->MATRIX_MCFG[2] = 1;
+  MATRIX->MATRIX_SCFG[0] = 0x01000010;
+  MATRIX->MATRIX_SCFG[1] = 0x01000010;
+  MATRIX->MATRIX_SCFG[7] = 0x01000010;
+#endif  // USE_SAM3X_BUS_MATRIX_FIX
+#endif  // USE_SAM3X_DMAC
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::deactivate() {
+  SPI.endTransaction();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::end() {
+  SPI.end();
+}
+//------------------------------------------------------------------------------
+static inline uint8_t spiTransfer(uint8_t b) {
+  Spi* pSpi = SPI0;
+
+  pSpi->SPI_TDR = b;
+  while ((pSpi->SPI_SR & SPI_SR_RDRF) == 0) {}
+  b = pSpi->SPI_RDR;
+  return b;
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive() {
+  return spiTransfer(0XFF);
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) {
+  Spi* pSpi = SPI0;
+  int rtn = 0;
+#if USE_SAM3X_DMAC
+  // clear overrun error
+  while (pSpi->SPI_SR & (SPI_SR_OVRES | SPI_SR_RDRF)) {pSpi->SPI_RDR;}
+  spiDmaRX(buf, count);
+  spiDmaTX(0, count);
+
+  uint32_t m = millis();
+  while (!dmac_channel_transfer_done(SPI_DMAC_RX_CH)) {
+    if ((millis() - m) > SAM3X_DMA_TIMEOUT)  {
+      dmac_channel_disable(SPI_DMAC_RX_CH);
+      dmac_channel_disable(SPI_DMAC_TX_CH);
+      rtn = 2;
+      break;
+    }
+  }
+  if (pSpi->SPI_SR & SPI_SR_OVRES) {
+    rtn |= 1;
+  }
+#else  // USE_SAM3X_DMAC
+  for (size_t i = 0; i < count; i++) {
+    pSpi->SPI_TDR = 0XFF;
+    while ((pSpi->SPI_SR & SPI_SR_RDRF) == 0) {}
+    buf[i] = pSpi->SPI_RDR;
+  }
+#endif  // USE_SAM3X_DMAC
+  return rtn;
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(uint8_t data) {
+  spiTransfer(data);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(const uint8_t* buf , size_t count) {
+  Spi* pSpi = SPI0;
+#if USE_SAM3X_DMAC
+  spiDmaTX(buf, count);
+  while (!dmac_channel_transfer_done(SPI_DMAC_TX_CH)) {}
+#else  // #if USE_SAM3X_DMAC
+  while ((pSpi->SPI_SR & SPI_SR_TXEMPTY) == 0) {}
+  for (size_t i = 0; i < count; i++) {
+    pSpi->SPI_TDR = buf[i];
+    while ((pSpi->SPI_SR & SPI_SR_TDRE) == 0) {}
+  }
+#endif  // #if USE_SAM3X_DMAC
+  while ((pSpi->SPI_SR & SPI_SR_TXEMPTY) == 0) {}
+  // leave RDR empty
+  while (pSpi->SPI_SR & (SPI_SR_OVRES | SPI_SR_RDRF)) {pSpi->SPI_RDR;}
+}
+#endif  // defined(SD_USE_CUSTOM_SPI) && defined(ARDUINO_SAM_DUE)

+ 96 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiESP.cpp

@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "SdSpiDriver.h"
+#if defined(SD_USE_CUSTOM_SPI) && (defined(ESP8266) || defined(ESP32))
+#define ESP_UNALIGN_OK 1
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::activate() {
+  m_spi->beginTransaction(m_spiSettings);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) {
+  if (spiConfig.spiPort) {
+    m_spi = spiConfig.spiPort;
+#if defined(SDCARD_SPI) && defined(SDCARD_SS_PIN)
+  } else if (spiConfig.csPin == SDCARD_SS_PIN) {
+    m_spi = &SDCARD_SPI;
+#endif  // defined(SDCARD_SPI) && defined(SDCARD_SS_PIN)
+  } else {
+    m_spi = &SPI;
+  }
+  m_spi->begin();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::deactivate() {
+  m_spi->endTransaction();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::end() {
+  m_spi->end();
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive() {
+  return m_spi->transfer(0XFF);
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) {
+#if ESP_UNALIGN_OK
+  m_spi->transferBytes(nullptr, buf, count);
+#else  // ESP_UNALIGN_OK
+  // Adjust to 32-bit alignment.
+  while ((reinterpret_cast<uintptr_t>(buf) & 0X3) && count) {
+    *buf++ = m_spi->transfer(0xff);
+    count--;
+  }
+  // Do multiple of four byte transfers.
+  size_t n4 = 4*(count/4);
+  if (n4) {
+    m_spi->transferBytes(nullptr, buf, n4);
+  }
+  // Transfer up to three remaining bytes.
+  for (buf += n4, count -= n4; count; count--) {
+    *buf++ = m_spi->transfer(0xff);
+  }
+#endif  // ESP_UNALIGN_OK
+  return 0;
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(uint8_t data) {
+  m_spi->transfer(data);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(const uint8_t* buf , size_t count) {
+#if !ESP_UNALIGN_OK
+  // Adjust to 32-bit alignment.
+  while ((reinterpret_cast<uintptr_t>(buf) & 0X3) && count) {
+    SPI.transfer(*buf++);
+    count--;
+  }
+#endif  // #if ESP_UNALIGN_OK
+
+  m_spi->transferBytes(const_cast<uint8_t*>(buf), nullptr, count);
+}
+#endif  // defined(SD_USE_CUSTOM_SPI) && (defined(ESP8266) || defined(ESP32))

+ 90 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiLibDriver.h

@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+/**
+ * \file
+ * \brief Class using only simple SPI library functions.
+ */
+#ifndef SdSpiLibDriver_h
+#define SdSpiLibDriver_h
+//------------------------------------------------------------------------------
+inline void SdSpiArduinoDriver::activate() {
+  m_spi->beginTransaction(m_spiSettings);
+}
+//------------------------------------------------------------------------------
+inline void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) {
+  if (spiConfig.spiPort) {
+    m_spi = spiConfig.spiPort;
+#if defined(SDCARD_SPI) && defined(SDCARD_SS_PIN)
+  } else if (spiConfig.csPin == SDCARD_SS_PIN) {
+    m_spi = &SDCARD_SPI;
+#endif  // defined(SDCARD_SPI) && defined(SDCARD_SS_PIN)
+  } else {
+    m_spi = &SPI;
+  }
+  m_spi->begin();
+}
+//------------------------------------------------------------------------------
+inline void SdSpiArduinoDriver::end() {
+  m_spi->end();
+}
+//------------------------------------------------------------------------------
+inline void SdSpiArduinoDriver::deactivate() {
+  m_spi->endTransaction();
+}
+//------------------------------------------------------------------------------
+inline uint8_t SdSpiArduinoDriver::receive() {
+  return m_spi->transfer( 0XFF);
+}
+//------------------------------------------------------------------------------
+inline uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) {
+#if USE_SPI_ARRAY_TRANSFER
+  memset(buf, 0XFF, count);
+  m_spi->transfer(buf, count);
+#else  // USE_SPI_ARRAY_TRANSFER
+  for (size_t i = 0; i < count; i++) {
+    buf[i] = m_spi->transfer(0XFF);
+  }
+#endif  // USE_SPI_ARRAY_TRANSFER
+  return 0;
+}
+//------------------------------------------------------------------------------
+inline void SdSpiArduinoDriver::send(uint8_t data) {
+  m_spi->transfer(data);
+}
+//------------------------------------------------------------------------------
+inline void SdSpiArduinoDriver::send(const uint8_t* buf, size_t count) {
+#if USE_SPI_ARRAY_TRANSFER
+  if (count <= 512) {
+    uint8_t tmp[512];
+    memcpy(tmp, buf, count);
+    m_spi->transfer(tmp, count);
+  }
+#else  // USE_SPI_ARRAY_TRANSFER
+  for (size_t i = 0; i < count; i++) {
+    m_spi->transfer(buf[i]);
+  }
+#endif  // USE_SPI_ARRAY_TRANSFER
+}
+#endif  // SdSpiLibDriver_h

+ 78 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiParticle.cpp

@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "SdSpiDriver.h"
+#if defined(SD_USE_CUSTOM_SPI) && defined(PLATFORM_ID)
+static volatile bool SPI_DMA_TransferCompleted = false;
+//-----------------------------------------------------------------------------
+static void SD_SPI_DMA_TransferComplete_Callback() {
+  SPI_DMA_TransferCompleted = true;
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::activate() {
+  m_spi->beginTransaction(m_spiSettings);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) {
+  if (spiConfig.spiPort) {
+    m_spi = spiConfig.spiPort;
+  } else {
+    m_spi = &SPI;
+  }
+  m_spi->begin();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::deactivate() {
+  m_spi->endTransaction();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::end() {
+  m_spi->end();
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive() {
+  return m_spi->transfer(0XFF);
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) {
+  SPI_DMA_TransferCompleted = false;
+  m_spi->transfer(nullptr, buf, count, SD_SPI_DMA_TransferComplete_Callback);
+  while (!SPI_DMA_TransferCompleted) {}
+  return 0;
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(uint8_t data) {
+  m_spi->transfer(data);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(const uint8_t* buf , size_t count) {
+  SPI_DMA_TransferCompleted = false;
+
+  m_spi->transfer(const_cast<uint8_t*>(buf), nullptr, count,
+                            SD_SPI_DMA_TransferComplete_Callback);
+
+  while (!SPI_DMA_TransferCompleted) {}
+}
+#endif  // defined(SD_USE_CUSTOM_SPI) && defined(PLATFORM_ID)
+

+ 82 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiSTM32.cpp

@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+// Driver for: https://github.com/rogerclarkmelbourne/Arduino_STM32
+#include "SdSpiDriver.h"
+#if defined(SD_USE_CUSTOM_SPI)\
+  && (defined(__STM32F1__) || defined(__STM32F4__))
+#if defined(__STM32F1__)
+#define USE_STM32_DMA 1
+#elif defined(__STM32F4__)
+#define USE_STM32_DMA 1
+#else  // defined(__STM32F1__)
+#error Unknown STM32 type
+#endif  // defined(__STM32F1__)
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::activate() {
+  m_spi->beginTransaction(m_spiSettings);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) {
+  if (spiConfig.spiPort) {
+    m_spi = spiConfig.spiPort;
+  } else {
+    m_spi = &SPI;
+  }
+  m_spi->begin();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::deactivate() {
+  m_spi->endTransaction();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::end() {
+  m_spi->end();
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive() {
+  return m_spi->transfer(0XFF);
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) {
+#if USE_STM32_DMA
+  return m_spi->dmaTransfer(nullptr, buf, count);
+#else  // USE_STM32_DMA
+  m_spi->read(buf, count);
+  return 0;
+#endif  // USE_STM32_DMA
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(uint8_t data) {
+  m_spi->transfer(data);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(const uint8_t* buf , size_t count) {
+#if USE_STM32_DMA
+  m_spi->dmaTransfer(const_cast<uint8*>(buf), nullptr, count);
+#else  // USE_STM32_DMA
+  m_spi->write(const_cast<uint8*>(buf), count);
+#endif  // USE_STM32_DMA
+}
+#endif  // defined(SD_USE_CUSTOM_SPI) &&  defined(__STM32F1__)

+ 75 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiSTM32Core.cpp

@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+// Driver for: https://github.com/stm32duino/Arduino_Core_STM32
+#include "SdSpiDriver.h"
+#if defined(SD_USE_CUSTOM_SPI) && defined(STM32_CORE_VERSION)
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::activate() {
+  m_spi->beginTransaction(m_spiSettings);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) {
+  if (spiConfig.spiPort) {
+    m_spi = spiConfig.spiPort;
+  } else {
+    m_spi = &SPI;
+  }
+  m_spi->begin();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::deactivate() {
+  m_spi->endTransaction();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::end() {
+  m_spi->end();
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive() {
+  return m_spi->transfer(0XFF);
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) {
+  // Must send 0XFF - SD looks at send data for command.
+  memset(buf, 0XFF, count);
+  m_spi->transfer(buf, count);
+  return 0;
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(uint8_t data) {
+  m_spi->transfer(data);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(const uint8_t* buf, size_t count) {
+  // Avoid stack overflow if bad count.  This should cause a write error.
+  if (count > 512) {
+    return;
+  }
+  // Not easy to avoid receive so use tmp RX buffer.
+  uint8_t rxBuf[512];
+  // Discard const - STM32 not const correct.
+  m_spi->transfer(const_cast<uint8_t*>(buf), rxBuf, count);
+}
+#endif  // defined(SD_USE_CUSTOM_SPI) && defined(STM32_CORE_VERSION)

+ 121 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiSoftDriver.h

@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+/**
+ * \file
+ * \brief Class for software SPI.
+ */
+#ifndef SdSpiSoftDriver_h
+#define SdSpiSoftDriver_h
+#include "../DigitalIO/SoftSPI.h"
+/**
+ * \class SdSpiSoftDriver
+ * \brief Base class for external soft SPI.
+ */
+class SdSpiSoftDriver {
+ public:
+  /** Activate SPI hardware. */
+  void activate() {}
+  /** Initialize the SPI bus. */
+  virtual void begin() = 0;
+  /** Initialize the SPI bus.
+   *
+   * \param[in] spiConfig SD card configuration.
+   */
+  void begin(SdSpiConfig spiConfig) {
+    (void)spiConfig;
+    begin();
+  }
+  /** Deactivate SPI hardware. */
+  void deactivate() {}
+  /** deactivate SPI driver. */
+  void end() {}
+  /** Receive a byte.
+   *
+   * \return The byte.
+   */
+  virtual uint8_t receive() = 0;
+  /** Receive multiple bytes.
+   *
+   * \param[out] buf Buffer to receive the data.
+   * \param[in] count Number of bytes to receive.
+   *
+   * \return Zero for no error or nonzero error code.
+   */
+  uint8_t receive(uint8_t* buf, size_t count) {
+    for (size_t i = 0; i < count; i++) {
+      buf[i] = receive();
+    }
+    return 0;
+  }
+  /** Send a byte.
+   *
+   * \param[in] data Byte to send
+   */
+  virtual void send(uint8_t data) = 0;
+  /** Send multiple bytes.
+   *
+   * \param[in] buf Buffer for data to be sent.
+   * \param[in] count Number of bytes to send.
+   */
+  void send(const uint8_t* buf, size_t count) {
+    for (size_t i = 0; i < count; i++) {
+      send(buf[i]);
+    }
+  }
+  /** Save high speed SPISettings after SD initialization.
+   *
+   * \param[in] maxSck Maximum SCK frequency.
+   */
+  void setSckSpeed(uint32_t maxSck) {
+    (void)maxSck;
+  }
+};
+//------------------------------------------------------------------------------
+/**
+ * \class SoftSpiDriver
+ * \brief Class for external soft SPI.
+ */
+template<uint8_t MisoPin, uint8_t MosiPin, uint8_t SckPin>
+class SoftSpiDriver : public SdSpiSoftDriver {
+ public:
+  /** Initialize the SPI bus. */
+  void begin() {m_spi.begin();}
+  /** Receive a byte.
+   *
+   * \return The byte.
+   */
+  uint8_t receive() {return m_spi.receive();}
+  /** Send a byte.
+   *
+   * \param[in] data Byte to send
+   */
+  void send(uint8_t data) {m_spi.send(data);}
+ private:
+  SoftSPI<MisoPin, MosiPin, SckPin, 0> m_spi;
+};
+
+/** Typedef for use of SdSoftSpiDriver */
+typedef SdSpiSoftDriver SdSpiDriver;
+#endif  // SdSpiSoftDriver_h

+ 90 - 0
lib/SdFat_NoArduino/src/SpiDriver/SdSpiTeensy3.cpp

@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "SdSpiDriver.h"
+#if defined(SD_USE_CUSTOM_SPI) &&  defined(__arm__) && defined(CORE_TEENSY)
+#define USE_BLOCK_TRANSFER 1
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::activate() {
+  m_spi->beginTransaction(m_spiSettings);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) {
+  if (spiConfig.spiPort) {
+    m_spi = spiConfig.spiPort;
+#if defined(SDCARD_SPI) && defined(SDCARD_SS_PIN)
+  } else if (spiConfig.csPin == SDCARD_SS_PIN) {
+    m_spi = &SDCARD_SPI;
+    m_spi->setMISO(SDCARD_MISO_PIN);
+    m_spi->setMOSI(SDCARD_MOSI_PIN);
+    m_spi->setSCK(SDCARD_SCK_PIN);
+#endif  // defined(SDCARD_SPI) && defined(SDCARD_SS_PIN)
+  } else {
+    m_spi = &SPI;
+  }
+  m_spi->begin();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::deactivate() {
+  m_spi->endTransaction();
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::end() {
+  m_spi->end();
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive() {
+  return m_spi->transfer(0XFF);
+}
+//------------------------------------------------------------------------------
+uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) {
+#if USE_BLOCK_TRANSFER
+  memset(buf, 0XFF, count);
+  m_spi->transfer(buf, count);
+#else  // USE_BLOCK_TRANSFER
+  for (size_t i = 0; i < count; i++) {
+    buf[i] = m_spi->transfer(0XFF);
+  }
+#endif  // USE_BLOCK_TRANSFER
+  return 0;
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(uint8_t data) {
+  m_spi->transfer(data);
+}
+//------------------------------------------------------------------------------
+void SdSpiArduinoDriver::send(const uint8_t* buf , size_t count) {
+#if USE_BLOCK_TRANSFER
+  uint32_t tmp[128];
+  if (0 < count && count <= 512) {
+    memcpy(tmp, buf, count);
+    m_spi->transfer(tmp, count);
+    return;
+  }
+#endif  // USE_BLOCK_TRANSFER
+  for (size_t i = 0; i < count; i++) {
+    m_spi->transfer(buf[i]);
+  }
+}
+#endif  // defined(SD_USE_CUSTOM_SPI) && defined(__arm__) &&defined(CORE_TEENSY)

+ 159 - 0
lib/SdFat_NoArduino/src/common/ArduinoFiles.h

@@ -0,0 +1,159 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef ArduinoFiles_h
+#define ArduinoFiles_h
+#include "SysCall.h"
+//------------------------------------------------------------------------------
+/** Arduino SD.h style flag for open for read. */
+#ifndef FILE_READ
+#define FILE_READ O_RDONLY
+#endif  // FILE_READ
+/** Arduino SD.h style flag for open at EOF for read/write with create. */
+#ifndef FILE_WRITE
+#define FILE_WRITE (O_RDWR | O_CREAT | O_AT_END)
+#endif  // FILE_WRITE
+//------------------------------------------------------------------------------
+/**
+ * \class PrintFile
+ * \brief PrintFile class.
+ */
+template<class BaseFile>
+class PrintFile : public print_t, public BaseFile {
+ public:
+  using BaseFile::clearWriteError;
+  using BaseFile::getWriteError;
+  using BaseFile::read;
+  using BaseFile::write;
+  /** Write a single byte.
+   * \param[in] b byte to write.
+   * \return one for success.
+   */
+  size_t write(uint8_t b) {
+    return BaseFile::write(&b, 1);
+  }
+};
+//------------------------------------------------------------------------------
+/**
+ * \class StreamFile
+ * \brief StreamFile class.
+ */
+template<class BaseFile, typename PosType>
+class StreamFile : public stream_t, public BaseFile {
+ public:
+  using BaseFile::clearWriteError;
+  using BaseFile::getWriteError;
+  using BaseFile::read;
+  using BaseFile::write;
+
+  StreamFile() {}
+
+  /** \return number of bytes available from the current position to EOF
+   *   or INT_MAX if more than INT_MAX bytes are available.
+   */
+  int available() {
+    return BaseFile::available();
+  }
+  /** Ensure that any bytes written to the file are saved to the SD card. */
+  void flush() {
+    BaseFile::sync();
+  }
+  /** This function reports if the current file is a directory or not.
+  * \return true if the file is a directory.
+  */
+  bool isDirectory() {
+    return BaseFile::isDir();
+  }
+  /** No longer implemented due to Long File Names.
+   *
+   * Use getName(char* name, size_t size).
+   * \return a pointer to replacement suggestion.
+   */
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+  char* __attribute__((error("use getName(name, size)"))) name();
+#endif  // DOXYGEN_SHOULD_SKIP_THIS
+  /** Return the next available byte without consuming it.
+   *
+   * \return The byte if no error and not at eof else -1;
+   */
+  int peek() {
+    return BaseFile::peek();
+  }
+  /** \return the current file position. */
+  PosType position() {
+    return BaseFile::curPosition();
+  }
+  /** Read the next byte from a file.
+   *
+   * \return For success return the next byte in the file as an int.
+   * If an error occurs or end of file is reached return -1.
+   */
+  int read() {
+    return BaseFile::read();
+  }
+  /** Rewind a file if it is a directory */
+  void rewindDirectory() {
+    if (BaseFile::isDir()) {
+      BaseFile::rewind();
+    }
+  }
+  /**
+   * Seek to a new position in the file, which must be between
+   * 0 and the size of the file (inclusive).
+   *
+   * \param[in] pos the new file position.
+   * \return true for success or false for failure.
+   */
+  bool seek(PosType pos) {
+    return BaseFile::seekSet(pos);
+  }
+  /** \return the file's size. */
+  PosType size() {
+    return BaseFile::fileSize();
+  }
+  /** Write a byte to a file. Required by the Arduino Print class.
+   * \param[in] b the byte to be written.
+   * Use getWriteError to check for errors.
+   * \return 1 for success and 0 for failure.
+   */
+  size_t write(uint8_t b) {
+    return BaseFile::write(b);
+  }
+  /** Write data to an open file.
+   *
+   * \note Data is moved to the cache but may not be written to the
+   * storage device until sync() is called.
+   *
+   * \param[in] buffer Pointer to the location of the data to be written.
+   *
+   * \param[in] size Number of bytes to write.
+   *
+   * \return For success write() returns the number of bytes written, always
+   * \a size.
+   */
+  size_t write(const uint8_t* buffer, size_t size) {
+    return BaseFile::write(buffer, size);
+  }
+};
+#endif  // ArduinoFiles_h

+ 74 - 0
lib/SdFat_NoArduino/src/common/CompileDateTime.h

@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef CompileDateTime_h
+#define CompileDateTime_h
+// Note - these functions will compile to a few bytes
+//        since they are evaluated at compile time.
+
+/** \return year field of the __DATE__ macro. */
+constexpr uint16_t compileYear() {
+  return  1000*(__DATE__[7] - '0')
+         + 100*(__DATE__[8] - '0')
+          + 10*(__DATE__[9] - '0')
+             + (__DATE__[10] - '0');
+}
+/** \return true if str equals the month field of the __DATE__ macro. */
+constexpr bool compileMonthIs(const char* str) {
+  return __DATE__[0] == str[0]
+      && __DATE__[1] == str[1]
+      && __DATE__[2] == str[2];
+}
+/** \return month field of the __DATE__ macro. */
+constexpr uint8_t compileMonth() {
+  return compileMonthIs("Jan") ? 1 :
+         compileMonthIs("Feb") ? 2 :
+         compileMonthIs("Mar") ? 3 :
+         compileMonthIs("Apr") ? 4 :
+         compileMonthIs("May") ? 5 :
+         compileMonthIs("Jun") ? 6 :
+         compileMonthIs("Jul") ? 7 :
+         compileMonthIs("Aug") ? 8 :
+         compileMonthIs("Sep") ? 9 :
+         compileMonthIs("Oct") ? 10 :
+         compileMonthIs("Nov") ? 11 :
+         compileMonthIs("Dec") ? 12 : 0;
+}
+/** \return day field of the __DATE__ macro. */
+constexpr uint8_t compileDay() {
+  return 10*(__DATE__[4] == ' ' ? 0 : __DATE__[4] - '0') + (__DATE__[5] - '0');
+}
+/** \return hour field of the __TIME__ macro. */
+constexpr uint8_t compileHour() {
+  return 10*(__TIME__[0] - '0') + __TIME__[1] - '0';
+}
+/** \return minute field of the __TIME__ macro. */
+constexpr uint8_t compileMinute() {
+  return 10*(__TIME__[3] - '0') + __TIME__[4] - '0';
+}
+/** \return second field of the __TIME__ macro. */
+constexpr uint8_t compileSecond() {
+  return 10*(__TIME__[6] - '0') + __TIME__[7] - '0';
+}
+#endif  // CompileDateTime_h

+ 74 - 0
lib/SdFat_NoArduino/src/common/DebugMacros.h

@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef DebugMacros_h
+#define DebugMacros_h
+#include "SysCall.h"
+
+// 0 - disable, 1 - fail, halt 2 - fail, halt, warn
+#define USE_DBG_MACROS 0
+
+#if USE_DBG_MACROS
+#include "Arduino.h"
+#ifndef DBG_FILE
+#error DBG_FILE not defined
+#endif  // DBG_FILE
+
+__attribute__((unused)) static void dbgFail(uint16_t line) {
+  Serial.print(F("DBG_FAIL: "));
+  Serial.print(F(DBG_FILE));
+  Serial.write('.');
+  Serial.println(line);
+}
+__attribute__((unused)) static void dbgHalt(uint16_t line) {
+  Serial.print(F("DBG_HALT: "));
+  Serial.print(F(DBG_FILE));
+  Serial.write('.');
+  Serial.println(line);
+  while (true) {}
+}
+#define DBG_FAIL_MACRO dbgFail(__LINE__)
+#define DBG_HALT_MACRO dbgHalt(__LINE__)
+#define DBG_HALT_IF(b) if (b) {dbgHalt(__LINE__);}
+
+#else  // USE_DBG_MACROS
+#define DBG_FAIL_MACRO
+#define DBG_HALT_MACRO
+#define DBG_HALT_IF(b)
+#endif  // USE_DBG_MACROS
+
+#if USE_DBG_MACROS > 1
+__attribute__((unused)) static void dbgWarn(uint16_t line) {
+  Serial.print(F("DBG_WARN: "));
+  Serial.print(F(DBG_FILE));
+  Serial.write('.');
+  Serial.println(line);
+}
+#define DBG_WARN_MACRO dbgWarn(__LINE__)
+#define DBG_WARN_IF(b) if (b) {dbgWarn(__LINE__);}
+#else  // USE_DBG_MACROS > 1
+#define DBG_WARN_MACRO
+#define DBG_WARN_IF(b)
+#endif  // USE_DBG_MACROS > 1
+#endif  // DebugMacros_h

+ 515 - 0
lib/SdFat_NoArduino/src/common/FmtNumber.cpp

@@ -0,0 +1,515 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "FmtNumber.h"
+// always use fmtBase10() - seems fast even on teensy 3.6.
+#define USE_FMT_BASE10 1
+
+// Use Stimmer div/mod 10 on avr
+#ifdef __AVR__
+#include <avr/pgmspace.h>
+#define USE_STIMMER
+#endif  // __AVR__
+//------------------------------------------------------------------------------
+// Stimmer div/mod 10 for AVR
+// this code fragment works out i/10 and i%10 by calculating
+// i*(51/256)*(256/255)/2 == i*51/510 == i/10
+// by "j.k" I mean 32.8 fixed point, j is integer part, k is fractional part
+// j.k = ((j+1.0)*51.0)/256.0
+// (we add 1 because we will be using the floor of the result later)
+// divmod10_asm16 and divmod10_asm32 are public domain code by Stimmer.
+// http://forum.arduino.cc/index.php?topic=167414.msg1293679#msg1293679
+#define divmod10_asm16(in32, mod8, tmp8)    \
+asm volatile(          \
+      " ldi %2,51     \n\t"     \
+      " mul %A0,%2    \n\t"     \
+      " clr %A0       \n\t"     \
+      " add r0,%2     \n\t"     \
+      " adc %A0,r1    \n\t"     \
+      " mov %1,r0     \n\t"     \
+      " mul %B0,%2    \n\t"     \
+      " clr %B0       \n\t"     \
+      " add %A0,r0    \n\t"     \
+      " adc %B0,r1    \n\t"     \
+      " clr r1        \n\t"     \
+      " add %1,%A0    \n\t"     \
+      " adc %A0,%B0   \n\t"     \
+      " adc %B0,r1   \n\t"      \
+      " add %1,%B0    \n\t"     \
+      " adc %A0,r1   \n\t"      \
+      " adc %B0,r1    \n\t"     \
+      " lsr %B0       \n\t"     \
+      " ror %A0       \n\t"     \
+      " ror %1        \n\t"     \
+      " ldi %2,10     \n\t"     \
+      " mul %1,%2     \n\t"     \
+      " mov %1,r1     \n\t"     \
+      " clr r1        \n\t"     \
+      :"+r"(in32), "=d"(mod8), "=d"(tmp8) : : "r0")
+
+#define divmod10_asm32(in32, mod8, tmp8)    \
+asm volatile(          \
+      " ldi %2,51     \n\t"     \
+      " mul %A0,%2    \n\t"     \
+      " clr %A0       \n\t"     \
+      " add r0,%2     \n\t"     \
+      " adc %A0,r1    \n\t"     \
+      " mov %1,r0     \n\t"     \
+      " mul %B0,%2    \n\t"     \
+      " clr %B0       \n\t"     \
+      " add %A0,r0    \n\t"     \
+      " adc %B0,r1    \n\t"     \
+      " mul %C0,%2    \n\t"     \
+      " clr %C0       \n\t"     \
+      " add %B0,r0    \n\t"     \
+      " adc %C0,r1    \n\t"     \
+      " mul %D0,%2    \n\t"     \
+      " clr %D0       \n\t"     \
+      " add %C0,r0    \n\t"     \
+      " adc %D0,r1    \n\t"     \
+      " clr r1        \n\t"     \
+      " add %1,%A0    \n\t"     \
+      " adc %A0,%B0   \n\t"     \
+      " adc %B0,%C0   \n\t"     \
+      " adc %C0,%D0   \n\t"     \
+      " adc %D0,r1    \n\t"     \
+      " add %1,%B0    \n\t"     \
+      " adc %A0,%C0   \n\t"     \
+      " adc %B0,%D0   \n\t"     \
+      " adc %C0,r1    \n\t"     \
+      " adc %D0,r1    \n\t"     \
+      " add %1,%D0    \n\t"     \
+      " adc %A0,r1    \n\t"     \
+      " adc %B0,r1    \n\t"     \
+      " adc %C0,r1    \n\t"     \
+      " adc %D0,r1    \n\t"     \
+      " lsr %D0       \n\t"     \
+      " ror %C0       \n\t"     \
+      " ror %B0       \n\t"     \
+      " ror %A0       \n\t"     \
+      " ror %1        \n\t"     \
+      " ldi %2,10     \n\t"     \
+      " mul %1,%2     \n\t"     \
+      " mov %1,r1     \n\t"     \
+      " clr r1        \n\t"     \
+      :"+r"(in32), "=d"(mod8), "=d"(tmp8) : : "r0")
+//------------------------------------------------------------------------------
+/*
+// C++ code is based on this version of divmod10 by robtillaart.
+// http://forum.arduino.cc/index.php?topic=167414.msg1246851#msg1246851
+// from robtillaart post:
+// The code is based upon the divu10() code from the book Hackers Delight1.
+// My insight was that the error formula in divu10() was in fact modulo 10
+// but not always. Sometimes it was 10 more.
+void divmod10(uint32_t in, uint32_t &div, uint32_t &mod)
+{
+  // q = in * 0.8;
+  uint32_t q = (in >> 1) + (in >> 2);
+  q = q + (q >> 4);
+  q = q + (q >> 8);
+  q = q + (q >> 16);  // not needed for 16 bit version
+
+  // q = q / 8;  ==> q =  in *0.1;
+  q = q >> 3;
+
+  // determine error
+  uint32_t r = in - ((q << 3) + (q << 1));   // r = in - q*10;
+  div = q + (r > 9);
+  if (r > 9) mod = r - 10;
+  else mod = r;
+}
+// See: https://github.com/hcs0/Hackers-Delight
+// Code below uses 8/10 = 0.1100 1100 1100 1100 1100 1100 1100 1100.
+// 15 ops including the multiply, or 17 elementary ops.
+unsigned divu10(unsigned n) {
+   unsigned q, r;
+
+   q = (n >> 1) + (n >> 2);
+   q = q + (q >> 4);
+   q = q + (q >> 8);
+   q = q + (q >> 16);
+   q = q >> 3;
+   r = n - q*10;
+   return q + ((r + 6) >> 4);
+// return q + (r > 9);
+}
+*/
+//------------------------------------------------------------------------------
+// Format 16-bit unsigned
+char* fmtBase10(char* str, uint16_t n) {
+  while (n > 9) {
+#ifdef USE_STIMMER
+    uint8_t tmp8, r;
+    divmod10_asm16(n, r, tmp8);
+#else  // USE_STIMMER
+    uint16_t t = n;
+    n = (n >> 1) + (n >> 2);
+    n = n + (n >> 4);
+    n = n + (n >> 8);
+    // n = n + (n >> 16);  // no code for 16-bit n
+    n = n >> 3;
+    uint8_t r = t - (((n << 2) + n) << 1);
+    if (r > 9) {
+      n++;
+      r -= 10;
+    }
+#endif  // USE_STIMMER
+    *--str = r + '0';
+  }
+  *--str = n + '0';
+  return str;
+}
+//------------------------------------------------------------------------------
+// format 32-bit unsigned
+char* fmtBase10(char* str, uint32_t n) {
+  while (n > 0XFFFF) {
+#ifdef USE_STIMMER
+    uint8_t tmp8, r;
+    divmod10_asm32(n, r, tmp8);
+#else  //  USE_STIMMER
+    uint32_t t = n;
+    n = (n >> 1) + (n >> 2);
+    n = n + (n >> 4);
+    n = n + (n >> 8);
+    n = n + (n >> 16);
+    n = n >> 3;
+    uint8_t r = t - (((n << 2) + n) << 1);
+    if (r > 9) {
+      n++;
+      r -= 10;
+    }
+#endif  // USE_STIMMER
+    *--str = r + '0';
+  }
+  return fmtBase10(str, (uint16_t)n);
+}
+//------------------------------------------------------------------------------
+char* fmtHex(char* str, uint32_t n) {
+  do {
+    uint8_t h = n & 0XF;
+    *--str = h + (h < 10 ? '0' : 'A' - 10);
+    n >>= 4;
+  } while (n);
+  return str;
+}
+//------------------------------------------------------------------------------
+char* fmtSigned(char* str, int32_t num, uint8_t base, bool caps) {
+  bool neg = base == 10 && num < 0;
+  if (neg) {
+    num = -num;
+  }
+  str = fmtUnsigned(str, num, base, caps);
+  if (neg) {
+    *--str = '-';
+  }
+  return str;
+}
+//-----------------------------------------------------------------------------
+char* fmtUnsigned(char* str, uint32_t num, uint8_t base, bool caps) {
+#if USE_FMT_BASE10
+  if (base == 10) return fmtBase10(str, (uint32_t)num);
+#endif  // USE_FMT_BASE10
+  do {
+    int c = num%base;
+    *--str = c + (c < 10 ? '0' : caps ? 'A' - 10 : 'a' - 10);
+  } while (num /= base);
+  return str;
+}
+//-----------------------------------------------------------------------------
+
+static const double powTen[] = {1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9};
+static const double rnd[] =
+  {5e-1, 5e-2, 5e-3, 5e-4, 5e-5, 5e-6, 5e-7, 5e-8, 5e-9, 5e-10};
+static const size_t MAX_PREC = sizeof(powTen)/sizeof(powTen[0]);
+
+char *fmtDouble(char *str, double num, uint8_t prec, bool altFmt) {
+  bool neg = num < 0;
+  if (neg) {
+    num = -num;
+  }
+  if (isnan(num)) {
+    *--str = 'n';
+    *--str = 'a';
+    *--str = 'n';
+    return str;
+  }
+  if (isinf(num)) {
+    *--str = 'f';
+    *--str = 'n';
+    *--str = 'i';
+    return str;
+  }
+  // last float < 2^32
+  if (num > 4294967040.0) {
+    *--str = 'f';
+    *--str = 'v';
+    *--str = 'o';
+    return str;
+  }
+
+  if (prec > MAX_PREC) {
+    prec = MAX_PREC;
+  }
+  num += rnd[prec];
+  uint32_t ul = num;
+  if (prec) {
+    char* s = str - prec;
+    uint32_t f = (num - ul)*powTen[prec - 1];
+    str = fmtBase10(str, f);
+    while (str > s) {
+      *--str = '0';
+    }
+  }
+  if (prec || altFmt) {
+    *--str = '.';
+  }
+  str = fmtBase10(str, ul);
+  if (neg) {
+    *--str = '-';
+  }
+  return str;
+}
+//------------------------------------------------------------------------------
+/** Print a number followed by a field terminator.
+ * \param[in] value The number to be printed.
+ * \param[in] ptr Pointer to last char in buffer.
+ * \param[in] prec Number of digits after decimal point.
+ * \param[in] expChar Use exp format if non zero.
+ * \return Pointer to first character of result.
+ */
+char* fmtDouble(char* str, double value,
+                uint8_t prec, bool altFmt, char expChar) {
+  if (expChar != 'e' && expChar != 'E') {
+    expChar = 0;
+  }
+  bool neg = value < 0;
+  if (neg) {
+    value = -value;
+  }
+  // check for nan inf ovf
+  if (isnan(value)) {
+    *--str = 'n';
+    *--str = 'a';
+    *--str = 'n';
+    return str;
+  }
+  if (isinf(value)) {
+    *--str = 'f';
+    *--str = 'n';
+    *--str = 'i';
+    return str;
+  }
+  if (!expChar && value > 4294967040.0) {
+    *--str = 'f';
+    *--str = 'v';
+    *--str = 'o';
+    return str;
+  }
+  if (prec > 9) {
+    prec = 9;
+  }
+  if (expChar) {
+    int8_t exp = 0;
+    bool expNeg = false;
+    if (value) {
+      if (value > 10.0L) {
+        while (value > 1e16L) {
+          value *= 1e-16L;
+          exp += 16;
+        }
+        while (value > 1e4L) {
+          value *= 1e-4L;
+          exp += 4;
+        }
+        while (value > 10.0L) {
+          value *= 0.1L;
+          exp++;
+        }
+      } else if (value < 1.0L) {
+         while (value < 1e-16L) {
+          value *= 1e16L;
+          exp -= 16;
+        }
+         while (value < 1e-4L) {
+          value *= 1e4L;
+          exp -= 4;
+        }
+        while (value < 1.0L) {
+          value *= 10.0L;
+          exp--;
+        }
+      }
+      value += rnd[prec];
+      if (value >= 10.0L) {
+        value *= 0.1L;
+        exp++;
+      }
+      expNeg = exp < 0;
+      if (expNeg) {
+        exp = -exp;
+      }
+    }
+    str = fmtBase10(str, (uint16_t)exp);
+    if (exp < 10) {
+      *--str = '0';
+    }
+    *--str = expNeg ? '-' : '+';
+    *--str = expChar;
+  } else {
+    // round value
+    value += rnd[prec];
+  }
+
+  uint32_t whole = value;
+  if (prec) {
+    char* tmp = str - prec;
+    uint32_t fraction = (value - whole)*powTen[prec - 1];
+    str = fmtBase10(str, fraction);
+    while (str > tmp) {
+      *--str = '0';
+    }
+  }
+  if (prec || altFmt)*--str = '.';
+  str = fmtBase10(str, whole);
+  if (neg) {
+    *--str = '-';
+  }
+  return str;
+}
+//==============================================================================
+//  functions below not used
+//------------------------------------------------------------------------------
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+#ifdef __AVR__
+static const float m[] PROGMEM = {1e-1, 1e-2, 1e-4, 1e-8, 1e-16, 1e-32};
+static const float p[] PROGMEM = {1e+1, 1e+2, 1e+4, 1e+8, 1e+16, 1e+32};
+#else  // __AVR__
+static const float m[] = {1e-1, 1e-2, 1e-4, 1e-8, 1e-16, 1e-32};
+static const float p[] = {1e+1, 1e+2, 1e+4, 1e+8, 1e+16, 1e+32};
+#endif  // __AVR__
+#endif  // DOXYGEN_SHOULD_SKIP_THIS
+// scale float v by power of ten. return v*10^n
+float scale10(float v, int8_t n) {
+  const float *s;
+  if (n < 0) {
+    n = -n;
+    s = m;
+  } else {
+    s = p;
+  }
+  n &= 63;
+  for (uint8_t i = 0; n; n >>= 1, i++) {
+#ifdef __AVR__
+    if (n & 1) {
+      v *= pgm_read_float(&s[i]);
+    }
+#else  // __AVR__
+    if (n & 1) {
+      v *= s[i];
+    }
+#endif  // __AVR__
+  }
+  return v;
+}
+//------------------------------------------------------------------------------
+float scanFloat(const char* str, const char** ptr) {
+  int16_t const EXP_LIMIT = 100;
+  bool digit = false;
+  bool dot = false;
+  uint32_t fract = 0;
+  int fracExp = 0;
+  uint8_t nd = 0;
+  bool neg;
+  int c;
+  float v;
+  const char* successPtr = str;
+
+  if (ptr) {
+    *ptr = str;
+  }
+
+  while (isSpace((c = *str++))) {}
+  neg = c == '-';
+  if (c == '-' || c == '+') {
+    c = *str++;
+  }
+  // Skip leading zeros
+  while (c == '0') {
+    c = *str++;
+    digit = true;
+  }
+  for (;;) {
+    if (isDigit(c)) {
+      digit = true;
+      if (nd < 9) {
+        fract = 10*fract + c - '0';
+        nd++;
+        if (dot) {
+          fracExp--;
+        }
+      } else {
+        if (!dot) {
+          fracExp++;
+        }
+      }
+    } else if (c == '.') {
+      if (dot) {
+        goto fail;
+      }
+      dot = true;
+    } else {
+      if (!digit) {
+        goto fail;
+      }
+      break;
+    }
+    successPtr = str;
+    c = *str++;
+  }
+  if (c == 'e' || c == 'E') {
+    int exp = 0;
+    c = *str++;
+    bool expNeg = c == '-';
+    if (c == '-' || c == '+') {
+      c = *str++;
+    }
+    while (isDigit(c)) {
+      if (exp > EXP_LIMIT) {
+        goto fail;
+      }
+      exp = 10*exp + c - '0';
+      successPtr = str;
+      c = *str++;
+    }
+    fracExp += expNeg ? -exp : exp;
+  }
+  if (ptr) {
+    *ptr = successPtr;
+  }
+  v = scale10(static_cast<float>(fract), fracExp);
+  return neg ? -v : v;
+
+ fail:
+  return 0;
+}

+ 43 - 0
lib/SdFat_NoArduino/src/common/FmtNumber.h

@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FmtNumber_h
+#define FmtNumber_h
+#include <math.h>
+#include <stdint.h>
+#include <stddef.h>
+inline bool isDigit(char c) {
+  return '0' <= (c) && (c) <= '9';
+}
+inline bool isSpace(char c) {
+  return (c) == ' ' || (0X9 <= (c) && (c) <= 0XD);
+}
+char* fmtBase10(char* str, uint16_t n);
+char* fmtBase10(char* str, uint32_t n);
+char* fmtDouble(char *str, double d, uint8_t prec, bool altFmt);
+char* fmtDouble(char* str, double d, uint8_t prec, bool altFmt, char expChar);
+char* fmtHex(char* str, uint32_t n);
+char* fmtSigned(char* str, int32_t n, uint8_t base, bool caps);
+char* fmtUnsigned(char* str, uint32_t n, uint8_t base, bool caps);
+#endif  // FmtNumber_h

+ 85 - 0
lib/SdFat_NoArduino/src/common/FsApiConstants.h

@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FsApiConstants_h
+#define FsApiConstants_h
+#include "SysCall.h"
+#if USE_FCNTL_H
+#include <fcntl.h>
+/* values for GNU Arm Embedded Toolchain.
+ * O_RDONLY:   0x0
+ * O_WRONLY:   0x1
+ * O_RDWR:     0x2
+ * O_ACCMODE:  0x3
+ * O_APPEND:   0x8
+ * O_CREAT:    0x200
+ * O_TRUNC:    0x400
+ * O_EXCL:     0x800
+ * O_SYNC:     0x2000
+ * O_NONBLOCK: 0x4000
+ */
+/** Use O_NONBLOCK for open at EOF */
+#define O_AT_END O_NONBLOCK  ///< Open at EOF.
+typedef int oflag_t;
+#else  // USE_FCNTL_H
+#define O_RDONLY  0X00  ///< Open for reading only.
+#define O_WRONLY  0X01  ///< Open for writing only.
+#define O_RDWR    0X02  ///< Open for reading and writing.
+#define O_AT_END  0X04  ///< Open at EOF.
+#define O_APPEND  0X08  ///< Set append mode.
+#define O_CREAT   0x10  ///< Create file if it does not exist.
+#define O_TRUNC   0x20  ///< Truncate file to zero length.
+#define O_EXCL    0x40  ///< Fail if the file exists.
+#define O_SYNC    0x80  ///< Synchronized write I/O operations.
+
+#define O_ACCMODE (O_RDONLY|O_WRONLY|O_RDWR)  ///< Mask for access mode.
+typedef uint8_t oflag_t;
+#endif  // USE_FCNTL_H
+
+#define O_READ    O_RDONLY
+#define O_WRITE   O_WRONLY
+
+inline bool isWriteMode(oflag_t oflag) {
+  oflag &= O_ACCMODE;
+  return oflag == O_WRONLY || oflag == O_RDWR;
+}
+
+// flags for ls()
+/** ls() flag for list all files including hidden. */
+const uint8_t LS_A = 1;
+/** ls() flag to print modify. date */
+const uint8_t LS_DATE = 2;
+/** ls() flag to print file size. */
+const uint8_t LS_SIZE = 4;
+/** ls() flag for recursive list of subdirectories */
+const uint8_t LS_R = 8;
+
+// flags for time-stamp
+/** set the file's last access date */
+const uint8_t T_ACCESS = 1;
+/** set the file's creation date and time */
+const uint8_t T_CREATE = 2;
+/** Set the file's write date and time */
+const uint8_t T_WRITE = 4;
+#endif  // FsApiConstants_h

+ 33 - 0
lib/SdFat_NoArduino/src/common/FsBlockDevice.h

@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FsBlockDevice_h
+#define FsBlockDevice_h
+#include "SdCard/SdCard.h"
+#if HAS_SDIO_CLASS || USE_BLOCK_DEVICE_INTERFACE
+typedef FsBlockDeviceInterface FsBlockDevice;
+#else
+typedef SdCard FsBlockDevice;
+#endif
+#endif  // FsBlockDevice_h

+ 95 - 0
lib/SdFat_NoArduino/src/common/FsBlockDeviceInterface.h

@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+/**
+ * \file
+ * \brief FsBlockDeviceInterface include file.
+ */
+#ifndef FsBlockDeviceInterface_h
+#define FsBlockDeviceInterface_h
+#include <stdint.h>
+#include <stddef.h>
+/**
+ * \class FsBlockDeviceInterface
+ * \brief FsBlockDeviceInterface class.
+ */
+class FsBlockDeviceInterface {
+ public:
+  virtual ~FsBlockDeviceInterface() {}
+
+  /** end use of device */
+  virtual void end() {}
+  /**
+   * Check for FsBlockDevice busy.
+   *
+   * \return true if busy else false.
+   */
+  virtual bool isBusy() = 0;
+  /**
+   * Read a sector.
+   *
+   * \param[in] sector Logical sector to be read.
+   * \param[out] dst Pointer to the location that will receive the data.
+   * \return true for success or false for failure.
+   */
+  virtual bool readSector(uint32_t sector, uint8_t* dst) = 0;
+
+  /**
+   * Read multiple sectors.
+   *
+   * \param[in] sector Logical sector to be read.
+   * \param[in] ns Number of sectors to be read.
+   * \param[out] dst Pointer to the location that will receive the data.
+   * \return true for success or false for failure.
+   */
+  virtual bool readSectors(uint32_t sector, uint8_t* dst, size_t ns) = 0;
+
+  /** \return device size in sectors. */
+  virtual uint32_t sectorCount() = 0;
+
+  /** End multi-sector transfer and go to idle state.
+   * \return true for success or false for failure.
+   */
+  virtual bool syncDevice() = 0;
+
+  /**
+   * Writes a sector.
+   *
+   * \param[in] sector Logical sector to be written.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \return true for success or false for failure.
+   */
+  virtual bool writeSector(uint32_t sector, const uint8_t* src) = 0;
+
+  /**
+   * Write multiple sectors.
+   *
+   * \param[in] sector Logical sector to be written.
+   * \param[in] ns Number of sectors to be written.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \return true for success or false for failure.
+   */
+  virtual bool writeSectors(uint32_t sector, const uint8_t* src, size_t ns) = 0;
+};
+#endif  // FsBlockDeviceInterface_h

+ 75 - 0
lib/SdFat_NoArduino/src/common/FsCache.cpp

@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#define DBG_FILE "FsCache.cpp"
+#include "DebugMacros.h"
+#include "FsCache.h"
+//------------------------------------------------------------------------------
+uint8_t* FsCache::prepare(uint32_t sector, uint8_t option) {
+  if (!m_blockDev) {
+    DBG_FAIL_MACRO;
+    goto fail;
+  }
+  if (m_sector != sector) {
+    if (!sync()) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    if (!(option & CACHE_OPTION_NO_READ)) {
+      if (!m_blockDev->readSector(sector, m_buffer)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+    m_status = 0;
+    m_sector = sector;
+  }
+  m_status |= option & CACHE_STATUS_MASK;
+  return m_buffer;
+
+ fail:
+  return nullptr;
+}
+//------------------------------------------------------------------------------
+bool FsCache::sync() {
+  if (m_status & CACHE_STATUS_DIRTY) {
+    if (!m_blockDev->writeSector(m_sector, m_buffer)) {
+      DBG_FAIL_MACRO;
+      goto fail;
+    }
+    // mirror second FAT
+    if (m_status & CACHE_STATUS_MIRROR_FAT) {
+      uint32_t sector = m_sector + m_mirrorOffset;
+      if (!m_blockDev->writeSector(sector, m_buffer)) {
+        DBG_FAIL_MACRO;
+        goto fail;
+      }
+    }
+    m_status &= ~CACHE_STATUS_DIRTY;
+  }
+  return true;
+
+ fail:
+  return false;
+}

+ 184 - 0
lib/SdFat_NoArduino/src/common/FsCache.h

@@ -0,0 +1,184 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FsCache_h
+#define FsCache_h
+/**
+ * \file
+ * \brief Common cache code for exFAT and FAT.
+ */
+#include "SysCall.h"
+#include "FsBlockDevice.h"
+/**
+ * \class FsCache
+ * \brief Sector cache.
+ */
+class FsCache {
+ public:
+  /** Cached sector is dirty */
+  static const uint8_t CACHE_STATUS_DIRTY = 1;
+  /** Cashed sector is FAT entry and must be mirrored in second FAT. */
+  static const uint8_t CACHE_STATUS_MIRROR_FAT = 2;
+  /** Cache sector status bits */
+  static const uint8_t CACHE_STATUS_MASK =
+    CACHE_STATUS_DIRTY | CACHE_STATUS_MIRROR_FAT;
+  /** Sync existing sector but do not read new sector. */
+  static const uint8_t CACHE_OPTION_NO_READ = 4;
+  /** Cache sector for read. */
+  static const uint8_t CACHE_FOR_READ = 0;
+  /** Cache sector for write. */
+  static const uint8_t CACHE_FOR_WRITE = CACHE_STATUS_DIRTY;
+  /** Reserve cache sector for write - do not read from sector device. */
+  static const uint8_t CACHE_RESERVE_FOR_WRITE =
+    CACHE_STATUS_DIRTY | CACHE_OPTION_NO_READ;
+  //----------------------------------------------------------------------------
+  /** \return Cache buffer address. */
+  uint8_t* cacheBuffer() {
+    return m_buffer;
+  }
+  /**
+   * Cache safe read of a sector.
+   *
+   * \param[in] sector Logical sector to be read.
+   * \param[out] dst Pointer to the location that will receive the data.
+   * \return true for success or false for failure.
+   */
+  bool cacheSafeRead(uint32_t sector, uint8_t* dst) {
+    if (isCached(sector)) {
+      memcpy(dst, m_buffer, 512);
+      return true;
+    }
+    return m_blockDev->readSector(sector, dst);
+  }
+  /**
+   * Cache safe read of multiple sectors.
+   *
+   * \param[in] sector Logical sector to be read.
+   * \param[in] count Number of sectors to be read.
+   * \param[out] dst Pointer to the location that will receive the data.
+   * \return true for success or false for failure.
+   */
+  bool cacheSafeRead(uint32_t sector, uint8_t* dst, size_t count) {
+    if (isCached(sector, count) && !sync()) {
+      return false;
+    }
+    return m_blockDev->readSectors(sector, dst, count);
+  }
+  /**
+   * Cache safe write of a sectors.
+   *
+   * \param[in] sector Logical sector to be written.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \return true for success or false for failure.
+   */
+  bool cacheSafeWrite(uint32_t sector, const uint8_t* src) {
+    if (isCached(sector)) {
+      invalidate();
+    }
+    return m_blockDev->writeSector(sector, src);
+  }
+  /**
+   * Cache safe write of multiple sectors.
+   *
+   * \param[in] sector Logical sector to be written.
+   * \param[in] src Pointer to the location of the data to be written.
+   * \param[in] count Number of sectors to be written.
+   * \return true for success or false for failure.
+   */
+  bool cacheSafeWrite(uint32_t sector, const uint8_t* src, size_t count) {
+     if (isCached(sector, count)) {
+      invalidate();
+    }
+    return m_blockDev->writeSectors(sector, src, count);
+  }
+  /** \return Clear the cache and returns a pointer to the cache. */
+  uint8_t* clear() {
+    if (isDirty() && !sync()) {
+      return nullptr;
+    }
+    invalidate();
+    return m_buffer;
+  }
+  /** Set current sector dirty. */
+  void dirty() {
+    m_status |= CACHE_STATUS_DIRTY;
+  }
+  /** Initialize the cache.
+   * \param[in] blockDev Block device for this cache.
+   */
+  void init(FsBlockDevice* blockDev) {
+    m_blockDev = blockDev;
+    invalidate();
+  }
+  /** Invalidate current cache sector. */
+  void invalidate() {
+    m_status = 0;
+    m_sector = 0XFFFFFFFF;
+  }
+  /** Check if a sector is in the cache.
+   * \param[in] sector Sector to checked.
+   * \return true if the sector is cached.
+   */
+  bool isCached(uint32_t sector) const {return sector == m_sector;}
+   /** Check if the cache contains a sector from a range.
+   * \param[in] sector Start sector of the range.
+   * \param[in] count Number of sectors in the range.
+   * \return true if a sector in the range is cached.
+   */
+  bool isCached(uint32_t sector, size_t count) {
+    return sector <= m_sector && m_sector < (sector + count);
+  }
+  /** \return dirty status */
+  bool isDirty() {
+    return m_status & CACHE_STATUS_DIRTY;
+  }
+  /** Prepare cache to access sector.
+   * \param[in] sector Sector to read.
+   * \param[in] option mode for cached sector.
+   * \return Address of cached sector.
+   */
+  uint8_t* prepare(uint32_t sector, uint8_t option);
+  /** \return Logical sector number for cached sector. */
+  uint32_t sector() {
+    return m_sector;
+  }
+  /** Set the offset to the second FAT for mirroring.
+   * \param[in] offset Sector offset to second FAT.
+   */
+  void setMirrorOffset(uint32_t offset) {
+    m_mirrorOffset = offset;
+  }
+  /** Write current sector if dirty.
+   * \return true for success or false for failure.
+   */
+  bool sync();
+
+ private:
+  uint8_t m_status;
+  FsBlockDevice* m_blockDev;
+  uint32_t m_mirrorOffset;
+  uint32_t m_sector;
+  uint8_t m_buffer[512];
+};
+#endif  // FsCache_h

+ 175 - 0
lib/SdFat_NoArduino/src/common/FsDateTime.cpp

@@ -0,0 +1,175 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "SysCall.h"
+#include "FsDateTime.h"
+#include "FmtNumber.h"
+
+static void dateTimeMs10(uint16_t* date, uint16_t* time, uint8_t* ms10) {
+  *ms10 = 0;
+  FsDateTime::callback2(date, time);
+}
+//------------------------------------------------------------------------------
+/** Date time callback. */
+namespace FsDateTime {
+  void (*callback)(uint16_t* date, uint16_t* time, uint8_t* ms10) = nullptr;
+  void (*callback2)(uint16_t* date, uint16_t* time) = nullptr;
+  void clearCallback() {
+    callback = nullptr;
+  }
+  void setCallback(void (*dateTime)(uint16_t* date, uint16_t* time)) {
+    callback = dateTimeMs10;
+    callback2 = dateTime;
+  }
+  void setCallback(
+    void (*dateTime)(uint16_t* date, uint16_t* time, uint8_t* ms10)) {
+    callback = dateTime;
+  }
+}  // namespace FsDateTime
+//------------------------------------------------------------------------------
+static char* fsFmtField(char* str, uint16_t n, char sep) {
+  if (sep) {
+    *--str = sep;
+  }
+  str = fmtBase10(str, n);
+  if (n < 10) {
+    *--str = '0';
+  }
+  return str;
+}
+//------------------------------------------------------------------------------
+char* fsFmtDate(char* str, uint16_t date) {
+  str = fsFmtField(str, date & 31, 0);
+  date >>= 5;
+  str = fsFmtField(str, date & 15, '-');
+  date >>= 4;
+  return fsFmtField(str, 1980 + date, '-');
+}
+//------------------------------------------------------------------------------
+char* fsFmtTime(char* str, uint16_t time) {
+  time >>= 5;
+  str = fsFmtField(str, time & 63, 0);
+  return fsFmtField(str, time >> 6, ':');
+}
+//------------------------------------------------------------------------------
+char* fsFmtTime(char* str, uint16_t time, uint8_t sec100) {
+  str = fsFmtField(str, 2*(time & 31) + (sec100 < 100 ? 0 : 1), 0);
+  *--str = ':';
+  return fsFmtTime(str, time);
+}
+//------------------------------------------------------------------------------
+char* fsFmtTimeZone(char* str, int8_t tz) {
+  char sign;
+  if (tz & 0X80) {
+    if (tz & 0X40) {
+      sign = '-';
+      tz = -tz;
+    } else {
+      sign = '+';
+      tz &= 0X7F;
+    }
+    if (tz) {
+      str = fsFmtField(str, 15*(tz%4), 0);
+      str = fsFmtField(str, tz/4, ':');
+      *--str = sign;
+    }
+    *--str = 'C';
+    *--str = 'T';
+    *--str = 'U';
+  }
+  return str;
+}
+//------------------------------------------------------------------------------
+size_t fsPrintDate(print_t* pr, uint16_t date) {
+  // Allow YYYY-MM-DD
+  char buf[sizeof("YYYY-MM-DD") -1];
+  char* str = buf + sizeof(buf);
+  if (date) {
+    str = fsFmtDate(str, date);
+  } else {
+     do {
+      *--str = ' ';
+    } while (str > buf);
+  }
+  return pr->write(reinterpret_cast<uint8_t*>(str), buf + sizeof(buf) - str);
+}
+//------------------------------------------------------------------------------
+size_t fsPrintDateTime(print_t* pr, uint16_t date, uint16_t time) {
+  // Allow YYYY-MM-DD hh:mm
+  char buf[sizeof("YYYY-MM-DD hh:mm") -1];
+  char* str = buf + sizeof(buf);
+  if (date) {
+    str = fsFmtTime(str, time);
+    *--str = ' ';
+    str = fsFmtDate(str, date);
+  } else {
+    do {
+      *--str = ' ';
+    } while (str > buf);
+  }
+  return pr->write(reinterpret_cast<uint8_t*>(str), buf + sizeof(buf) - str);
+}
+//------------------------------------------------------------------------------
+size_t fsPrintDateTime(print_t* pr, uint32_t dateTime) {
+  return fsPrintDateTime(pr, dateTime >> 16, dateTime & 0XFFFF);
+}
+//------------------------------------------------------------------------------
+size_t fsPrintDateTime(print_t* pr,
+                       uint32_t dateTime, uint8_t s100, int8_t tz) {
+  // Allow YYYY-MM-DD hh:mm:ss UTC+hh:mm
+  char buf[sizeof("YYYY-MM-DD hh:mm:ss UTC+hh:mm") -1];
+  char* str = buf + sizeof(buf);
+  if (tz) {
+    str = fsFmtTimeZone(str, tz);
+    *--str = ' ';
+  }
+  str = fsFmtTime(str, (uint16_t)dateTime, s100);
+  *--str = ' ';
+  str = fsFmtDate(str, (uint16_t)(dateTime >> 16));
+  return pr->write(reinterpret_cast<uint8_t*>(str), buf + sizeof(buf) - str);
+}
+//------------------------------------------------------------------------------
+size_t fsPrintTime(print_t* pr, uint16_t time) {
+  // Allow hh:mm
+  char buf[sizeof("hh:mm") -1];
+  char* str = buf + sizeof(buf);
+  str = fsFmtTime(str, time);
+  return pr->write(reinterpret_cast<uint8_t*>(str), buf + sizeof(buf) - str);
+}
+//------------------------------------------------------------------------------
+size_t fsPrintTime(print_t* pr, uint16_t time, uint8_t sec100) {
+  // Allow hh:mm:ss
+  char buf[sizeof("hh:mm:ss") -1];
+  char* str = buf + sizeof(buf);
+  str = fsFmtTime(str, time, sec100);
+  return pr->write(reinterpret_cast<uint8_t*>(str), buf + sizeof(buf) - str);
+}
+//------------------------------------------------------------------------------
+size_t fsPrintTimeZone(print_t* pr, int8_t tz) {
+  // Allow UTC+hh:mm
+  char buf[sizeof("UTC+hh:mm") -1];
+  char* str = buf + sizeof(buf);
+  str = fsFmtTimeZone(str, tz);
+  return pr->write(reinterpret_cast<uint8_t*>(str), buf + sizeof(buf) - str);
+}

+ 193 - 0
lib/SdFat_NoArduino/src/common/FsDateTime.h

@@ -0,0 +1,193 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FsDateTime_h
+#define FsDateTime_h
+#include <stdint.h>
+#include "CompileDateTime.h"
+#include "SysCall.h"
+
+/** Backward compatible definition. */
+#define FAT_DATE(y, m, d) FS_DATE(y, m, d)
+
+/** Backward compatible definition. */
+#define FAT_TIME(h, m, s) FS_TIME(h, m, s)
+
+/** Date time callback */
+namespace FsDateTime {
+  /** Date time callback. */
+  extern void (*callback)(uint16_t* date, uint16_t* time, uint8_t* ms10);
+  /** Date time callback. */
+  extern void (*callback2)(uint16_t* date, uint16_t* time);
+  /** Cancel callback. */
+  void clearCallback();
+   /** Set the date/time callback function.
+   *
+   * \param[in] dateTime The user's call back function.  The callback.
+   * function is of the form:
+   *
+   * \code
+   * void dateTime(uint16_t* date, uint16_t* time) {
+   *   uint16_t year;
+   *   uint8_t month, day, hour, minute, second;
+   *
+   *   // User gets date and time from GPS or real-time clock here.
+   *
+   *   // Return date using FS_DATE macro to format fields.
+   *   *date = FS_DATE(year, month, day);
+   *
+   *   // Return time using FS_TIME macro to format fields.
+   *   *time = FS_TIME(hour, minute, second);
+   * }
+   * \endcode
+   *
+   * Sets the function that is called when a file is created or when
+   * a file's directory entry is modified by sync(). All timestamps,
+   * access, creation, and modify, are set when a file is created.
+   * sync() maintains the last access date and last modify date/time.
+   *
+   */
+  void setCallback(void (*dateTime)(uint16_t* date, uint16_t* time));
+   /** Set the date/time callback function.
+   *
+   * \param[in] dateTime The user's call back function.  The callback
+   * function is of the form:
+   *
+   * \code
+   * void dateTime(uint16_t* date, uint16_t* time, uint8_t* ms10) {
+   *   uint16_t year;
+   *   uint8_t month, day, hour, minute, second;
+   *
+   *   // User gets date and time from GPS or real-time clock here.
+   *
+   *   // Return date using FS_DATE macro to format fields
+   *   *date = FS_DATE(year, month, day);
+   *
+   *   // Return time using FS_TIME macro to format fields
+   *   *time = FS_TIME(hour, minute, second);
+   *
+   *   // Return the time since the last even second in units of 10 ms.
+   *   // The granularity of the seconds part of FS_TIME is 2 seconds so
+   *   // this field is a count of hundredth of a second and its valid
+   *   // range is 0-199 inclusive.
+   *   // For a simple RTC return 100*(seconds & 1).
+   *   *ms10 = <tens of ms since even second>
+   * }
+   * \endcode
+   *
+   * Sets the function that is called when a file is created or when
+   * a file's directory entry is modified by sync(). All timestamps,
+   * access, creation, and modify, are set when a file is created.
+   * sync() maintains the last access date and last modify date/time.
+   *
+   */
+  void setCallback(
+    void (*dateTime)(uint16_t* date, uint16_t* time, uint8_t* ms10));
+}  // namespace FsDateTime
+
+/** date field for directory entry
+ * \param[in] year [1980,2107]
+ * \param[in] month [1,12]
+ * \param[in] day [1,31]
+ *
+ * \return Packed date for directory entry.
+ */
+static inline uint16_t FS_DATE(uint16_t year, uint8_t month, uint8_t day) {
+  year -= 1980;
+  return year > 127 || month > 12 || day > 31 ? 0 :
+         year << 9 | month << 5 | day;
+}
+/** year part of FAT directory date field
+ * \param[in] fatDate Date in packed dir format.
+ *
+ * \return Extracted year [1980,2107]
+ */
+static inline uint16_t FS_YEAR(uint16_t fatDate) {
+  return 1980 + (fatDate >> 9);
+}
+/** month part of FAT directory date field
+ * \param[in] fatDate Date in packed dir format.
+ *
+ * \return Extracted month [1,12]
+ */
+static inline uint8_t FS_MONTH(uint16_t fatDate) {
+  return (fatDate >> 5) & 0XF;
+}
+/** day part of FAT directory date field
+ * \param[in] fatDate Date in packed dir format.
+ *
+ * \return Extracted day [1,31]
+ */
+static inline uint8_t FS_DAY(uint16_t fatDate) {
+  return fatDate & 0X1F;
+}
+/** time field for directory entry
+ * \param[in] hour [0,23]
+ * \param[in] minute [0,59]
+ * \param[in] second [0,59]
+ *
+ * \return Packed time for directory entry.
+ */
+static inline uint16_t FS_TIME(uint8_t hour, uint8_t minute, uint8_t second) {
+  return hour > 23 || minute > 59 || second > 59 ? 0 :
+         hour << 11 | minute << 5 | second >> 1;
+}
+/** hour part of FAT directory time field
+ * \param[in] fatTime Time in packed dir format.
+ *
+ * \return Extracted hour [0,23]
+ */
+static inline uint8_t FS_HOUR(uint16_t fatTime) {
+  return fatTime >> 11;
+}
+/** minute part of FAT directory time field
+ * \param[in] fatTime Time in packed dir format.
+ *
+ * \return Extracted minute [0,59]
+ */
+static inline uint8_t FS_MINUTE(uint16_t fatTime) {
+  return (fatTime >> 5) & 0X3F;
+}
+/** second part of FAT directory time field
+ * N\note second/2 is stored in packed time.
+ *
+ * \param[in] fatTime Time in packed dir format.
+ *
+ * \return Extracted second [0,58]
+ */
+static inline uint8_t FS_SECOND(uint16_t fatTime) {
+  return 2*(fatTime & 0X1F);
+}
+char* fsFmtDate(char* str, uint16_t date);
+char* fsFmtTime(char* str, uint16_t time);
+char* fsFmtTime(char* str, uint16_t time, uint8_t sec100);
+char* fsFmtTimeZone(char* str, int8_t tz);
+size_t fsPrintDate(print_t* pr, uint16_t date);
+size_t fsPrintDateTime(print_t* pr, uint16_t date, uint16_t time);
+size_t fsPrintDateTime(print_t* pr, uint32_t dateTime);
+size_t fsPrintDateTime(print_t* pr, uint32_t dateTime, uint8_t s100, int8_t tz);
+size_t fsPrintTime(print_t* pr, uint16_t time);
+size_t fsPrintTime(print_t* pr, uint16_t time, uint8_t sec100);
+size_t fsPrintTimeZone(print_t* pr, int8_t tz);
+#endif  // FsDateTime_h

+ 54 - 0
lib/SdFat_NoArduino/src/common/FsName.cpp

@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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 "FsName.h"
+#include "FsUtf.h"
+#if USE_UTF8_LONG_NAMES
+uint16_t FsName::get16() {
+  uint16_t rtn;
+  if (ls) {
+    rtn = ls;
+    ls = 0;
+  } else if (next >= end) {
+    rtn = 0;
+  } else {
+    uint32_t cp;
+    const char* ptr = FsUtf::mbToCp(next, end, &cp);
+    if (!ptr) {
+      goto fail;
+    }
+    next = ptr;
+    if (cp <= 0XFFFF) {
+      rtn = cp;
+    } else {
+      ls = FsUtf::lowSurrogate(cp);
+      rtn = FsUtf::highSurrogate(cp);
+    }
+  }
+  return rtn;
+
+ fail:
+  return 0XFFFF;
+}
+#endif  // USE_UTF8_LONG_NAMES

+ 66 - 0
lib/SdFat_NoArduino/src/common/FsName.h

@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2011-2022 Bill Greiman
+ * This file is part of the SdFat library for SD memory cards.
+ *
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef FsName_h
+#define FsName_h
+#include "SysCall.h"
+#include <stdint.h>
+/**
+ * \file
+ * \brief FsName class.
+ */
+/**
+ * \class FsName
+ * \brief Handle UTF-8 file names.
+ */
+class FsName {
+ public:
+  /** Beginning of LFN. */
+  const char* begin;
+  /** Next LFN character of end. */
+  const char* next;
+  /** Position one beyond last LFN character. */
+  const char* end;
+#if !USE_UTF8_LONG_NAMES
+  /** \return true if at end. */
+  bool atEnd() {return next == end;}
+  /** Reset to start of LFN. */
+  void reset() {next = begin;}
+  /** \return next char of LFN. */
+  char getch() {return atEnd() ? 0 : *next++;}
+  /** \return next UTF-16 unit of LFN. */
+  uint16_t get16() {return atEnd() ? 0 : *next++;}
+#else  // !USE_UTF8_LONG_NAMES
+  uint16_t ls = 0;
+  bool atEnd() {
+    return !ls && next == end;
+  }
+  void reset() {
+    next = begin;
+    ls = 0;  // lowSurrogate
+  }
+  uint16_t get16();
+#endif  // !USE_UTF8_LONG_NAMES
+};
+#endif  // FsName_h

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor