Quellcode durchsuchen

Merge remote-tracking branch 'zuul/main'

Restore attribution and commit history that was removed. History is important.
Remove claim in lincense that zulu is based on bluescsi. Git history clearly shows it is not.
Eric Helgeson vor 3 Jahren
Ursprung
Commit
84d204679a
100 geänderte Dateien mit 16422 neuen und 0 gelöschten Zeilen
  1. 59 0
      .github/workflows/firmware_build.yml
  2. 5 0
      .gitignore
  3. 9 0
      LICENSE
  4. 20 0
      Performance.md
  5. 143 0
      README.md
  6. 42 0
      boards/ZuluSCSI_RP2040.json
  7. 8 0
      greenpak/README.md
  8. 647 0
      greenpak/SCSI_Accelerator_SLG46824.gp6
  9. 17 0
      greenpak/SCSI_Accelerator_SLG46824.hex
  10. BIN
      greenpak/SCSI_Accelerator_SLG46824.png
  11. 158 0
      lib/SCSI2SD/CHANGELOG
  12. 63 0
      lib/SCSI2SD/COPYING
  13. 674 0
      lib/SCSI2SD/gplv3.txt
  14. 252 0
      lib/SCSI2SD/include/scsi2sd.h
  15. 13 0
      lib/SCSI2SD/library.json
  16. 324 0
      lib/SCSI2SD/src/firmware/cdrom.c
  17. 22 0
      lib/SCSI2SD/src/firmware/cdrom.h
  18. 30 0
      lib/SCSI2SD/src/firmware/config.h
  19. 247 0
      lib/SCSI2SD/src/firmware/diagnostic.c
  20. 26 0
      lib/SCSI2SD/src/firmware/diagnostic.h
  21. 58 0
      lib/SCSI2SD/src/firmware/disk.h
  22. 228 0
      lib/SCSI2SD/src/firmware/geometry.c
  23. 78 0
      lib/SCSI2SD/src/firmware/geometry.h
  24. 267 0
      lib/SCSI2SD/src/firmware/inquiry.c
  25. 25 0
      lib/SCSI2SD/src/firmware/inquiry.h
  26. 24 0
      lib/SCSI2SD/src/firmware/led.h
  27. 39 0
      lib/SCSI2SD/src/firmware/mo.c
  28. 22 0
      lib/SCSI2SD/src/firmware/mo.h
  29. 746 0
      lib/SCSI2SD/src/firmware/mode.c
  30. 22 0
      lib/SCSI2SD/src/firmware/mode.h
  31. 1283 0
      lib/SCSI2SD/src/firmware/scsi.c
  32. 202 0
      lib/SCSI2SD/src/firmware/scsi.h
  33. 48 0
      lib/SCSI2SD/src/firmware/sd.h
  34. 178 0
      lib/SCSI2SD/src/firmware/sense.h
  35. 29 0
      lib/SCSI2SD/src/firmware/tape.c
  36. 22 0
      lib/SCSI2SD/src/firmware/tape.h
  37. 58 0
      lib/SCSI2SD/src/firmware/vendor.c
  38. 22 0
      lib/SCSI2SD/src/firmware/vendor.h
  39. 22 0
      lib/SdFat_NoArduino/.gitattributes
  40. 215 0
      lib/SdFat_NoArduino/.gitignore
  41. 1 0
      lib/SdFat_NoArduino/.piopm
  42. 21 0
      lib/SdFat_NoArduino/LICENSE.md
  43. 110 0
      lib/SdFat_NoArduino/README.md
  44. 2685 0
      lib/SdFat_NoArduino/doc/Doxyfile
  45. 50 0
      lib/SdFat_NoArduino/doc/SdErrorCodes.txt
  46. 10 0
      lib/SdFat_NoArduino/doc/SdFat.html
  47. 4 0
      lib/SdFat_NoArduino/doc/ZipMsg/index.html
  48. 3 0
      lib/SdFat_NoArduino/doc/clean_html.bat
  49. 3 0
      lib/SdFat_NoArduino/doc/del_html.bat
  50. BIN
      lib/SdFat_NoArduino/doc/html.zip
  51. 4 0
      lib/SdFat_NoArduino/doc/html/index.html
  52. 289 0
      lib/SdFat_NoArduino/doc/mainpage.h
  53. 31 0
      lib/SdFat_NoArduino/examples/AvrAdcLogger/AvrAdcLogger.h
  54. 898 0
      lib/SdFat_NoArduino/examples/AvrAdcLogger/AvrAdcLogger.ino
  55. 83 0
      lib/SdFat_NoArduino/examples/BackwardCompatibility/BackwardCompatibility.ino
  56. 235 0
      lib/SdFat_NoArduino/examples/BufferedPrint/BufferedPrint.ino
  57. 158 0
      lib/SdFat_NoArduino/examples/DirectoryFunctions/DirectoryFunctions.ino
  58. 9 0
      lib/SdFat_NoArduino/examples/ExFatLogger/ExFatLogger.h
  59. 595 0
      lib/SdFat_NoArduino/examples/ExFatLogger/ExFatLogger.ino
  60. 58 0
      lib/SdFat_NoArduino/examples/MinimumSizeSdReader/MinimumSizeSdReader.ino
  61. 105 0
      lib/SdFat_NoArduino/examples/OpenNext/OpenNext.ino
  62. 187 0
      lib/SdFat_NoArduino/examples/QuickStart/QuickStart.ino
  63. 156 0
      lib/SdFat_NoArduino/examples/ReadCsvFile/ReadCsvFile.ino
  64. 236 0
      lib/SdFat_NoArduino/examples/RtcTimestampTest/RtcTimestampTest.ino
  65. 19 0
      lib/SdFat_NoArduino/examples/SdErrorCodes/SdErrorCodes.ino
  66. 249 0
      lib/SdFat_NoArduino/examples/SdFormatter/SdFormatter.ino
  67. 265 0
      lib/SdFat_NoArduino/examples/SdInfo/SdInfo.ino
  68. 80 0
      lib/SdFat_NoArduino/examples/SoftwareSpi/SoftwareSpi.ino
  69. 247 0
      lib/SdFat_NoArduino/examples/TeensyDmaAdcLogger/TeensyDmaAdcLogger.ino
  70. 139 0
      lib/SdFat_NoArduino/examples/TeensyRtcTimestamp/TeensyRtcTimestamp.ino
  71. 221 0
      lib/SdFat_NoArduino/examples/TeensySdioDemo/TeensySdioDemo.ino
  72. 148 0
      lib/SdFat_NoArduino/examples/TeensySdioLogger/TeensySdioLogger.ino
  73. 98 0
      lib/SdFat_NoArduino/examples/UnicodeFilenames/UnicodeFilenames.ino
  74. 47 0
      lib/SdFat_NoArduino/examples/UserChipSelectFunction/UserChipSelectFunction.ino
  75. 81 0
      lib/SdFat_NoArduino/examples/UserSPIDriver/UserSPIDriver.ino
  76. 276 0
      lib/SdFat_NoArduino/examples/bench/bench.ino
  77. 197 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/AnalogLogger/AnalogLogger.ino
  78. 46 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/BaseExtCaseTest/BaseExtCaseTest.ino
  79. 20 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/HelloWorld/HelloWorld.ino
  80. 29 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/MiniSerial/MiniSerial.ino
  81. 125 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/PrintBenchmarkSD/PrintBenchmarkSD.ino
  82. 30 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/SD_Size/SD_Size.ino
  83. 33 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/SdFatSize/SdFatSize.ino
  84. 44 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/StreamParseInt/StreamParseInt.ino
  85. 77 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/append/append.ino
  86. 82 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/average/average.ino
  87. 149 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/benchSD/benchSD.ino
  88. 39 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/bufstream/bufstream.ino
  89. 39 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/cin_cout/cin_cout.ino
  90. 62 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/eventlog/eventlog.ino
  91. 111 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/fgetsRewrite/fgetsRewrite.ino
  92. 51 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/readlog/readlog.ino
  93. 34 0
      lib/SdFat_NoArduino/examples/examplesV1/#attic/readme.txt
  94. 39 0
      lib/SdFat_NoArduino/examples/examplesV1/AnalogBinLogger/AnalogBinLogger.h
  95. 826 0
      lib/SdFat_NoArduino/examples/examplesV1/AnalogBinLogger/AnalogBinLogger.ino
  96. 129 0
      lib/SdFat_NoArduino/examples/examplesV1/DirectoryFunctions/DirectoryFunctions.ino
  97. 102 0
      lib/SdFat_NoArduino/examples/examplesV1/LongFileName/LongFileName.ino
  98. 4 0
      lib/SdFat_NoArduino/examples/examplesV1/LongFileName/testFiles/A long name can be 255 characters.txt
  99. 1 0
      lib/SdFat_NoArduino/examples/examplesV1/LongFileName/testFiles/LFN,NAME.TXT
  100. 5 0
      lib/SdFat_NoArduino/examples/examplesV1/LongFileName/testFiles/MIXCASE.txt

+ 59 - 0
.github/workflows/firmware_build.yml

@@ -0,0 +1,59 @@
+name: Build ZuluSCSI firmware
+
+on: 
+  push:
+  workflow_dispatch:
+
+jobs:
+  build_firmware:
+    name: Build firmware on Ubuntu 20.04
+    runs-on: ubuntu-20.04
+    
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+        with:
+          path: ZuluSCSI
+          fetch-depth: "0"
+      
+      - name: Install platformio
+        run: |
+          sudo pip install platformio
+      
+      - name: Build firmware
+        run: |
+          cd ZuluSCSI
+          pio run -v
+    
+      - name: Rename firmware files
+        run: |
+          cd ZuluSCSI
+          utils/rename_binaries.sh
+
+      - name: Upload binaries into build artifacts
+        uses: actions/upload-artifact@v2
+        with:
+          path: ZuluSCSI/distrib/*
+          name: ZuluSCSI binaries
+      
+      - name: Upload to latest release
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        if: github.ref == 'refs/heads/main'
+        run: |
+          cd ZuluSCSI
+          git tag -d latest
+          git tag latest
+          git push origin --force latest
+          cd distrib
+          gh api repos/${GITHUB_REPOSITORY}/releases/tags/latest | jq -r '.assets[] | [.url] | @tsv' | xargs -n 1 gh api -X DELETE || true
+          gh release upload --repo ${GITHUB_REPOSITORY} --clobber latest *
+
+      - name: Upload to newly created release
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        if: ${{ startsWith(github.ref, 'refs/tags/') }}
+        run: |
+          cd ZuluSCSI/distrib
+          RELEASE=$(basename ${{github.ref}})
+          gh release upload --repo ${GITHUB_REPOSITORY} $RELEASE *

+ 5 - 0
.gitignore

@@ -3,3 +3,8 @@
 .vscode/
 workspace.code-workspace
 dist/
+.vscode/.browse.c_cpp.db*
+.vscode/c_cpp_properties.json
+.vscode/launch.json
+.vscode/ipch
+.vscode/extensions.json

+ 9 - 0
LICENSE

@@ -36,6 +36,15 @@ https://www.gnu.org/licenses/gpl-3.0.html
 BlueSCSI to SD is a SCSI emulator.
 Copyright (C) 2021  Eric Helgeson
 
+ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+
+ZuluSCSI™ is based on both SCSI2SD V6A codebase,
+and licensed under the GPL version 3 or any later version.
+
+https://www.gnu.org/licenses/gpl-3.0.html
+----
+SCSI2SD code is Copyright (C) 2016-2022 Michael McMaster.
+
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or

+ 20 - 0
Performance.md

@@ -0,0 +1,20 @@
+Performance
+-----------
+Performance characteristics of ZuluSCSI differ depending on the hardware version. 
+
+Additionally, on older, slower computers, particularly 68000 Macs with 8-16MHz CPUs, performance will be gated by the speed of your CPU, and the local bus between the CPU and your SCSI controller.
+
+
+With ZuluSCSI V1.1, and verbose log messages disabled (debug DIP switch OFF), expected SCSI performance is ~3.5 MB/s read and ~2 MB/s write.
+
+With ZuluSCSI V1.0, and verbose log messages disabled (debug DIP switch OFF), expected SCSI performance is 2.4 MB/s read and 2 MB/s write, assuming you're using a fast enough machine, 
+With ZuluSCSI Mini (V1.0), and verbose log messages disabled (default, unless you enable it in zuluscsi.ini), 
+expected SCSI performance is 2.4 MB/s read and 2 MB/s write.
+
+Slow SD cards or a fragmented filesystem can slow down access. The use of Speed Class 4 SD cards may result in the bottleneck being the SD card itself. We recommend using Speed Class 10 or above SDHC-marked cards. 
+
+Seek performance is best if image files are contiguous.
+
+For ExFAT filesystem this relies on a file flag set by PC.
+Current versions of exfat-fuse on Linux have an [issue](https://github.com/relan/exfat/pull/101) that causes the files not to be marked contiguous even when they are.
+This is indicated by message `WARNING: file HD00_512.hda is not contiguous. This will increase read latency.` in the log.

+ 143 - 0
README.md

@@ -0,0 +1,143 @@
+ZuluSCSI™ Firmware
+=================
+
+Hard Drive & ISO image files
+---------------------
+ZuluSCSI uses raw hard drive image files, which are stored on a FAT32 or exFAT-formatted SD card. These are often referred to as "hda" files.
+
+Examples of valid filenames:
+* `HD5.hda` or `HD5.img`: hard drive with SCSI ID 5
+* `HD20_512.hda`: hard drive with SCSI ID 2, LUN 0, block size 512
+* `CD3.iso`: CD drive with SCSI ID 3
+
+In addition to the simplified filenames style above, the ZuluSCSI firmware also looks for images using the BlueSCSI-style "HDxy_512.hda" filename formatting.
+
+The media type can be set in `zuluscsi.ini`, or directly by the file name prefix.
+Supported prefixes are `HD` (hard drive), `CD` (cd-rom), `FD` (floppy), `MO` (magneto-optical), `RE` (generic removeable media), `TP` (sequential tape drive).
+
+Log files and error indications
+-------------------------------
+Log messages are stored in `zululog.txt`, which is cleared on every boot.
+Normally only basic initialization information is stored, but switching the `DBG` DIP switch on will cause every SCSI command to be logged, once the board is power cycled.
+
+The indicator LED will normally report disk access.
+It also reports following status conditions:
+
+- 1 fast blink on boot: Image file loaded successfully
+- 3 fast blinks: No images found on SD card
+- 5 fast blinks: SD card not detected
+- Continuous morse pattern: firmware crashed, morse code indicates crash location
+
+In crashes the firmware will also attempt to save information into `zuluerr.txt`.
+
+Configuration file
+------------------
+Optional configuration can be stored in `zuluscsi.ini`.
+If image file is found but configuration is missing, a default configuration is used.
+
+Example config file is available here: [zuluscsi.ini](zuluscsi.ini).
+
+Performance
+-----------
+Performance information for the various ZuluSCSI hardware models is [documented separately, here](Performance.md)
+
+Hotplugging
+-----------
+The firmware supports hot-plug removal and reinsertion of SD card.
+The status led will blink continuously when card is not present, then blink once when card is reinserted successfully.
+
+It will depend on the host system whether it gets confused by hotplugging.
+Any IO requests issued when card is removed will be timeouted.
+
+Programming & bootloader
+------------------------
+There is a bootloader that loads new firmware from SD card on boot.
+The firmware file must be e.g. `ZuluSCSI.bin` or `ZuluSCSIv1_0_2022-xxxxx.bin`.
+Firmware update takes about 1 second, during which the LED will flash rapidly.
+
+When successful, the bootloader removes the update file and continues to main firmware.
+On failure, `Zuluerr.txt` is written on the SD card.
+
+Alternatively, the board can be programmed using USB connection in DFU mode by setting DIP switch 4.
+The necessary programmer utility for Windows can be downloaded from [GD32 website](http://www.gd32mcu.com/en/download?kw=dfu&lan=en). On Linux and MacOS, the standard 'dfu-util' can be used. It can be installed via your package manager under Linux. On MacOS, it is available through MacPorts and Brew as a package.
+
+`dfu-util --alt 0 --dfuse-address 0x08000000 --download ZuluSCSIv1_1_XXXXXX.bin`
+
+For RP2040-based boards, the USB programming uses `.uf2` format file that can be copied to the USB drive that shows up in bootloader mode.
+
+DIP switches
+------------
+For ZuluSCSI V1.1, the DIP switch settings are as follows:
+
+- DEBUG: Enable verbose debug log (saved to `zululog.txt`)
+- TERM: Enable SCSI termination
+- BOOT: Enable built-in USB bootloader, this DIP switch MUST remain off during normal operation.
+- SW1: Enables/disables Macintosh/Apple specific mode-pages and device strings, which eases disk initialization when performing fresh installs on legacy Macintosh computers.
+
+ZuluSCSI Mini has no DIP switches, so all optional configuration parameters must be defined in zuluscsi.ini
+
+ZuluSCSI RP2040 DIP switch settings are:
+- INITIATOR: Enable SCSI initiator mode for imaging SCSI drives
+- DEBUG LOG: Enable verbose debug log (saved to `zululog.txt`)
+- TERMINATION: Enable SCSI termination
+- BOOTLOADER: Enable built-in USB bootloader, this DIP switch MUST remain off during normal operation.
+
+SCSI initiator mode
+-------------------
+The RP2040 model supports SCSI initiator mode for reading SCSI drives.
+When enabled by the DIP switch, ZuluSCSI RP2040 will scan for SCSI drives on the bus and copy the data as `HDxx_imaged.hda` to the SD card.
+
+LED indications in initiator mode:
+
+- Short blink once a second: idle, searching for SCSI drives
+- Fast blink 4 times per second: copying data. The blink acts as a progress bar: first it is short and becomes longer when data copying progresses.
+
+The firmware retries reads up to 5 times and attempts to skip any sectors that have problems.
+Any read errors are logged into `zululog.txt`.
+
+Depending on hardware setup, you may need to mount diode `D205` and jumper `JP201` to supply `TERMPWR` to the SCSI bus.
+This is necessary if the drives do not supply their own SCSI terminator power.
+
+Project structure
+-----------------
+- **src/ZuluSCSI.cpp**: Main portable SCSI implementation.
+- **src/ZuluSCSI_disk.cpp**: Interface between SCSI2SD code and SD card reading.
+- **src/ZuluSCSI_log.cpp**: Simple logging functionality, uses memory buffering.
+- **src/ZuluSCSI_config.h**: Some compile-time options, usually no need to change.
+- **lib/ZuluSCSI_platform_GD32F205**: Platform-specific code for GD32F205.
+- **lib/SCSI2SD**: SCSI2SD V6 code, used for SCSI command implementations.
+- **lib/minIni**: Ini config file access library
+- **lib/SdFat_NoArduino**: Modified version of [SdFat](https://github.com/greiman/SdFat) library for use without Arduino core.
+- **utils/run_gdb.sh**: Helper script for debugging with st-link adapter. Displays SWO log directly in console.
+
+To port the code to a new platform, see README in [lib/ZuluSCSI_platform_template](lib/ZuluSCSI_platform_template) folder.
+
+Building
+--------
+This codebase uses [PlatformIO](https://platformio.org/).
+To build run the command:
+
+    pio run
+
+
+Origins and License
+-------------------
+
+This firmware is derived from two sources, both under GPL 3 license:
+
+* [SCSI2SD V6](http://www.codesrc.com/mediawiki/index.php/SCSI2SD)
+* [BlueSCSI](https://github.com/erichelgeson/BlueSCSI), which in turn is derived from [ArdSCSIno-stm32](https://github.com/ztto/ArdSCSino-stm32).
+
+Main program structure:
+
+* SCSI command implementations are from SCSI2SD.
+* SCSI physical layer code is mostly custom, with some inspiration from BlueSCSI.
+* Image file access is derived from BlueSCSI.
+
+Major changes from BlueSCSI and SCSI2SD include:
+
+* Separation of platform-specific functionality to separate directory to ease porting.
+* Ported to GD32F205.
+* Removal of Arduino core dependency, as it was not currently available for GD32F205.
+* Buffered log functions.
+* Simultaneous transfer between SD card and SCSI for improved performance.

+ 42 - 0
boards/ZuluSCSI_RP2040.json

@@ -0,0 +1,42 @@
+{
+    "name": "ZuluSCSI RP2040",
+    "url": "https://github.com/ZuluSCSI/ZuluSCSI-firmware",
+    "vendor": "ZuluSCSI",
+    "build": {
+        "core": "arduino",
+        "cpu": "cortex-m0plus",
+        "extra_flags": "-DARDUINO_ARCH_RP2040",
+        "f_cpu": "133000000L",
+        "hwids": [
+        [
+            "0x2E8A",
+            "0x00C0"
+        ]
+        ],
+        "mcu": "rp2040",
+        "variant": "RASPBERRY_PI_PICO"
+    },
+    "debug": {
+        "jlink_device": "RP2040_M0_0",
+        "openocd_target": "rp2040.cfg",
+        "svd_path": "rp2040.svd"
+    },
+    "frameworks": [
+        "arduino"
+    ],
+    "upload": {
+        "maximum_ram_size": 270336,
+        "maximum_size": 2097152,
+        "require_upload_port": true,
+        "native_usb": true,
+        "use_1200bps_touch": true,
+        "wait_for_upload_port": false,
+        "protocol": "picotool",
+        "protocols": [
+        "cmsis-dap",
+        "jlink",
+        "raspberrypi-swd",
+        "picotool"
+        ]
+    }
+}

+ 8 - 0
greenpak/README.md

@@ -0,0 +1,8 @@
+GreenPAK design files
+=====================
+
+This folder contains design files for `SLG46824` programmable logic device.
+It is optionally used on ZuluSCSI V1.1 to speed up access to SCSI bus. This applies to asynchronous transfers only
+
+What this logic does is implement the `REQ` / `ACK` handshake in hardware.
+The CPU only has to write new data to the GPIO, and the external logic will toggle `REQ` signal quickly.

+ 647 - 0
greenpak/SCSI_Accelerator_SLG46824.gp6

@@ -0,0 +1,647 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<GPDProject version="22" oldestCompatibleVersion="22" GPDVersion="6.27.001" lastChange="11.4.2022 12.26">
+    <generalProjectSettings/>
+    <chip family="04" type="06" friendlyName="GreenPAK 6" partNumber="40" package="16">
+        <nvmData registerLenght="2048">C4 9 0 0 0 0 0 0 38 C2 0 0 0 0 0 0 0 0 0 0 0 0 D0 8 13 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 E7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 30 70 0 30 20 30 30 0 0 30 30 30 0 30 30 30 30 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 14 22 30 C 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 D7 14 20 0 1 0 0 0 2 1 0 0 2 0 1 0 0 2 1 0 0 2 0 1 0 0 2 1 0 0 2 0 1 0 0 2 0 1 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 A5</nvmData>
+        <checksum crc32="0xB44E9FD4" version="5"/>
+        <VDDItem id="0">
+            <item id="0" caption="VDD (PIN 20)">
+                <graphics pos="(-590.00,30.00)" angle="0" flipping="0" hidden="0" tOrigin="(20.00,10.00)"/>
+            </item>
+        </VDDItem>
+        <IOPad id="1" useCaseMode="0">
+            <item id="1" caption="PIN 19 (IO0)">
+                <graphics pos="(-590.00,100.00)" angle="0" flipping="0" hidden="0" tOrigin="(20.00,10.00)"/>
+                <textLabel pos="(-19.10,-27.00)" angle="0.00" textColor="#ffff00ff" backgroundColor="#14141464">PLD_IO0 / CLK</textLabel>
+            </item>
+        </IOPad>
+        <IOPad id="2" useCaseMode="1">
+            <item id="2" caption="PIN 18 (IO1)">
+                <graphics pos="(-248.00,278.00)" angle="0" flipping="0" hidden="0" tOrigin="(20.00,10.00)"/>
+                <textLabel pos="(-45.56,-27.00)" angle="0.00" textColor="#ffff00ff" backgroundColor="#14141464">PLD_IO1 / DATA_TOGGLE</textLabel>
+            </item>
+        </IOPad>
+        <IOPad id="3" useCaseMode="1">
+            <item id="3" caption="PIN 17 (IO2)">
+                <graphics pos="(-251.00,504.00)" angle="0" flipping="0" hidden="0" tOrigin="(20.00,10.00)"/>
+                <textLabel pos="(-29.23,-27.00)" angle="0.00" textColor="#ffff00ff" backgroundColor="#14141464">PLD_IO2 / ENABLE</textLabel>
+            </item>
+        </IOPad>
+        <IOPad id="4" useCaseMode="1">
+            <item id="4" caption="PIN 16 (IO3)">
+                <graphics pos="(-249.00,197.00)" angle="0" flipping="0" hidden="0" tOrigin="(20.00,10.00)"/>
+                <textLabel pos="(-25.28,-27.00)" angle="0.00" textColor="#ffff00ff" backgroundColor="#14141464">PLD_IO3 / RDWR</textLabel>
+            </item>
+        </IOPad>
+        <IOPad id="5" useCaseMode="0">
+            <item id="5" caption="PIN 15 (IO4)">
+                <graphics pos="(-590.00,380.00)" angle="0" flipping="0" hidden="0" tOrigin="(20.00,10.00)"/>
+                <textLabel pos="(-4.71,-27.00)" angle="0.00" textColor="#ffff00ff" backgroundColor="#14141464">PLD_IO4</textLabel>
+            </item>
+        </IOPad>
+        <IOPad id="6" useCaseMode="0">
+            <item id="6" caption="PIN 14 (IO5)">
+                <graphics pos="(-595.00,464.00)" angle="0" flipping="0" hidden="0" tOrigin="(20.00,10.00)"/>
+                <textLabel pos="(-4.71,-27.00)" angle="0.00" textColor="#ffff00ff" backgroundColor="#14141464">PLD_IO5</textLabel>
+            </item>
+        </IOPad>
+        <IOPad id="7" useCaseMode="1">
+            <item id="7" caption="PIN 13 (SCL)">
+                <graphics pos="(-764.00,671.00)" angle="0" flipping="0" hidden="0" tOrigin="(20.00,10.00)"/>
+            </item>
+        </IOPad>
+        <IOPad id="8" useCaseMode="1">
+            <item id="8" caption="PIN 12 (SDA)">
+                <graphics pos="(-764.00,701.00)" angle="0" flipping="0" hidden="0" tOrigin="(20.00,10.00)"/>
+            </item>
+        </IOPad>
+        <IOPad id="9" useCaseMode="0">
+            <item id="9" caption="PIN 11 (IO6)">
+                <graphics pos="(149.00,672.00)" angle="270" flipping="2" hidden="0" tOrigin="(20.00,10.00)"/>
+            </item>
+        </IOPad>
+        <item id="10" caption="GND (PIN 10)">
+            <graphics pos="(770.00,380.00)" angle="0" flipping="1" hidden="0" tOrigin="(20.00,10.00)"/>
+        </item>
+        <IOPad id="11" useCaseMode="0">
+            <item id="11" caption="PIN 9 (IO7)">
+                <graphics pos="(758.00,300.00)" angle="0" flipping="1" hidden="0" tOrigin="(20.00,10.00)"/>
+            </item>
+        </IOPad>
+        <IOPad id="12" useCaseMode="0">
+            <item id="12" caption="PIN 8 (IO8)">
+                <graphics pos="(692.00,505.00)" angle="0" flipping="1" hidden="0" tOrigin="(20.00,10.00)"/>
+                <textLabel pos="(-4.71,-27.00)" angle="0.00" textColor="#ffff00ff" backgroundColor="#14141464">PLD_IO8</textLabel>
+            </item>
+        </IOPad>
+        <VDDItem id="13">
+            <item id="13" caption="VDD2 (PIN 7)">
+                <graphics pos="(770.00,170.00)" angle="0" flipping="1" hidden="0" tOrigin="(20.00,10.00)"/>
+            </item>
+        </VDDItem>
+        <IOPad id="14" useCaseMode="0">
+            <item id="14" caption="PIN 6 (IO9)">
+                <graphics pos="(636.00,631.00)" angle="0" flipping="1" hidden="0" tOrigin="(20.00,10.00)"/>
+                <textLabel pos="(-4.71,-27.00)" angle="0.00" textColor="#ffff00ff" backgroundColor="#14141464">PLD_IO9</textLabel>
+            </item>
+        </IOPad>
+        <IOPad id="15" useCaseMode="0">
+            <item id="15" caption="PIN 5 (IO10)">
+                <graphics pos="(770.00,30.00)" angle="0" flipping="1" hidden="0" tOrigin="(20.00,10.00)"/>
+            </item>
+        </IOPad>
+        <IOPad id="16" useCaseMode="0">
+            <item id="16" caption="PIN 4 (IO11)">
+                <graphics pos="(300.00,-90.00)" angle="270" flipping="1" hidden="0" tOrigin="(20.00,10.00)"/>
+            </item>
+        </IOPad>
+        <IOPad id="17" useCaseMode="0">
+            <item id="17" caption="PIN 3 (IO12)">
+                <graphics pos="(150.00,-90.00)" angle="270" flipping="1" hidden="0" tOrigin="(20.00,10.00)"/>
+            </item>
+        </IOPad>
+        <IOPad id="18" useCaseMode="4">
+            <item id="18" caption="PIN 2 (IO13)">
+                <graphics pos="(411.00,320.00)" angle="0" flipping="1" hidden="0" tOrigin="(20.00,10.00)"/>
+                <textLabel pos="(-7.69,-27.00)" angle="0.00" textColor="#ffff00ff" backgroundColor="#14141464">OUT_REQ</textLabel>
+            </item>
+        </IOPad>
+        <IOPad id="19" useCaseMode="1">
+            <item id="19" caption="PIN 1 (IO14)">
+                <graphics pos="(-250.00,387.00)" angle="0" flipping="0" hidden="0" tOrigin="(20.00,10.00)"/>
+                <textLabel pos="(-6.55,-27.00)" angle="0.00" textColor="#ffff00ff" backgroundColor="#14141464">ACK_BUF</textLabel>
+            </item>
+        </IOPad>
+        <item id="22" caption="A CMP0L">
+            <graphics pos="(50.00,130.00)" angle="0" flipping="0" hidden="1" tOrigin="(25.00,28.00)"/>
+        </item>
+        <item id="23" caption="A CMP1L">
+            <graphics pos="(150.00,130.00)" angle="0" flipping="0" hidden="1" tOrigin="(25.00,28.00)"/>
+        </item>
+        <item id="24" caption="P DLY">
+            <graphics pos="(320.00,320.00)" angle="0" flipping="0" hidden="1" tOrigin="(17.50,10.00)"/>
+        </item>
+        <item id="26" caption="VREF">
+            <graphics pos="(320.00,190.00)" angle="0" flipping="0" hidden="1" tOrigin="(17.50,10.00)"/>
+        </item>
+        <item id="27" caption="POR">
+            <graphics pos="(320.00,260.00)" angle="0" flipping="0" hidden="1" tOrigin="(17.50,10.00)"/>
+        </item>
+        <item id="28" caption="OSC0">
+            <graphics pos="(-70.00,100.00)" angle="0" flipping="0" hidden="1" tOrigin="(30.00,40.00)"/>
+        </item>
+        <item id="29" caption="OSC1">
+            <graphics pos="(-70.00,200.00)" angle="0" flipping="0" hidden="1" tOrigin="(30.00,25.00)"/>
+        </item>
+        <item id="30" caption="OSC2">
+            <graphics pos="(-70.00,270.00)" angle="0" flipping="0" hidden="1" tOrigin="(30.00,20.00)"/>
+        </item>
+        <item id="31" caption="I2C">
+            <graphics pos="(-674.00,652.00)" angle="0" flipping="0" hidden="0" tOrigin="(25.00,45.00)"/>
+        </item>
+        <item id="33" caption="FILTER/EDGE DET">
+            <graphics pos="(320.00,440.00)" angle="0" flipping="0" hidden="1" tOrigin="(27.50,15.00)"/>
+        </item>
+        <item id="34" caption="BG">
+            <graphics pos="(320.00,30.00)" angle="0" flipping="0" hidden="1" tOrigin="(17.50,10.00)"/>
+        </item>
+        <LUT id="35" regularShape="0" mode="0">
+            <item id="35" caption="2-bit LUT0/DFF/LATCH0">
+                <graphics pos="(-88.00,177.00)" angle="90" flipping="0" hidden="0" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <LUT id="36" regularShape="0" mode="0">
+            <item id="36" caption="2-bit LUT1/DFF/LATCH1">
+                <graphics pos="(40.00,370.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <LUT id="37" regularShape="0" mode="0">
+            <item id="37" caption="2-bit LUT2/DFF/LATCH2">
+                <graphics pos="(40.00,430.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <LUT id="38" regularShape="0" mode="0">
+            <item id="38" caption="2-bit LUT3/PGEN">
+                <graphics pos="(130.00,250.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <LUT id="39" regularShape="0" mode="0">
+            <item id="39" caption="3-bit LUT0/DFF/LATCH3">
+                <graphics pos="(130.00,310.00)" angle="90" flipping="1" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <item id="40" caption="3-bit LUT1/DFF/LATCH4">
+            <graphics pos="(19.00,176.00)" angle="0" flipping="0" hidden="0" tOrigin="(25.00,10.00)"/>
+            <textLabel pos="(-15.00,-40.00)" angle="0.00" textColor="#ffff00ff" backgroundColor="#14141464">Previous
+DATA_TOGGLE</textLabel>
+        </item>
+        <LUT id="41" regularShape="0" mode="0">
+            <item id="41" caption="3-bit LUT2/DFF/LATCH5">
+                <graphics pos="(130.00,430.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <LUT id="42" regularShape="0" mode="0">
+            <item id="42" caption="3-bit LUT3/DFF/LATCH6">
+                <graphics pos="(210.00,250.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <LUT id="43" regularShape="0" mode="0">
+            <item id="43" caption="3-bit LUT4/DFF/LATCH7">
+                <graphics pos="(210.00,310.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <LUT id="44" regularShape="0" mode="0">
+            <item id="44" caption="3-bit LUT5/DFF/LATCH8">
+                <graphics pos="(210.00,370.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <LUT id="45" regularShape="0" mode="0">
+            <item id="45" caption="3-bit LUT6/Pipe Delay/Ripple Counter">
+                <graphics pos="(210.00,430.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <item id="46" caption="MF0 (4-bit LUT0, DFF/LATCH9, 16-bit CNT0/DLY0/FSM0)">
+            <graphics pos="(-399.00,35.00)" angle="0" flipping="0" hidden="1" tOrigin="(155.00,60.00)"/>
+        </item>
+        <item id="47" caption="MF1 (3-bit LUT7, DFF/LATCH10, 8-bit CNT1/DLY1)">
+            <graphics pos="(-500.00,140.00)" angle="0" flipping="0" hidden="1" tOrigin="(155.00,60.00)"/>
+        </item>
+        <item id="48" caption="MF2 (3-bit LUT8, DFF/LATCH11, 8-bit CNT2/DLY2)">
+            <graphics pos="(-500.00,300.00)" angle="0" flipping="0" hidden="1" tOrigin="(155.00,60.00)"/>
+        </item>
+        <item id="49" caption="MF3 (3-bit LUT9, DFF/LATCH12, 8-bit CNT3/DLY3)">
+            <graphics pos="(-500.00,460.00)" angle="0" flipping="0" hidden="1" tOrigin="(155.00,60.00)"/>
+        </item>
+        <item id="50" caption="MF4 (3-bit LUT10, DFF/LATCH13, 8-bit CNT4/DLY4)">
+            <graphics pos="(430.00,-30.00)" angle="0" flipping="0" hidden="1" tOrigin="(155.00,60.00)"/>
+        </item>
+        <item id="51" caption="MF5 (3-bit LUT11, DFF/LATCH14, 8-bit CNT5/DLY5)">
+            <graphics pos="(430.00,130.00)" angle="0" flipping="0" hidden="1" tOrigin="(155.00,60.00)"/>
+        </item>
+        <item id="52" caption="MF6 (3-bit LUT12, DFF/LATCH15, 8-bit CNT6/DLY6)">
+            <graphics pos="(430.00,290.00)" angle="0" flipping="0" hidden="1" tOrigin="(155.00,60.00)"/>
+        </item>
+        <item id="53" caption="MF7 (3-bit LUT13, DFF/LATCH16, 8-bit CNT7/DLY7)">
+            <graphics pos="(430.00,450.00)" angle="0" flipping="0" hidden="1" tOrigin="(155.00,60.00)"/>
+        </item>
+        <LUT id="54" regularShape="0" mode="0">
+            <item id="54" caption="4-bit LUT0 (MF0)">
+                <graphics pos="(136.00,310.00)" angle="90" flipping="0" hidden="0" tOrigin="(20.00,15.00)"/>
+                <textLabel pos="(46.00,52.19)" angle="-90.00" textColor="#ffff00ff" backgroundColor="#14141464">IN3 = 0 (Write to SCSI bus): !(IN0 &amp; (IN1 ^ IN2))
+IN3 = 1 (Read from SCSI bus): (!IN0) &amp; (IN1 ^ IN2)</textLabel>
+            </item>
+        </LUT>
+        <item id="55" caption="DFF/LATCH9 (MF0)">
+            <graphics pos="(-357.50,94.50)" angle="0" flipping="0" hidden="1" tOrigin="(25.00,10.00)"/>
+        </item>
+        <CNTDLY id="56" externalClockFrequence="0" externalClockFreqUnit="kHz">
+            <item id="56" caption="16-bit CNT0/DLY0/FSM0 (MF0)">
+                <graphics pos="(-208.50,87.50)" angle="0" flipping="0" hidden="1" tOrigin="(35.00,16.00)"/>
+            </item>
+        </CNTDLY>
+        <LUT id="57" regularShape="0" mode="0">
+            <item id="57" caption="3-bit LUT7 (MF1)">
+                <graphics pos="(-365.00,185.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <item id="58" caption="DFF/LATCH10 (MF1)">
+            <graphics pos="(-450.00,130.00)" angle="0" flipping="0" hidden="1" tOrigin="(25.00,10.00)"/>
+        </item>
+        <CNTDLY id="59" externalClockFrequence="0" externalClockFreqUnit="kHz">
+            <item id="59" caption="8-bit CNT1/DLY1 (MF1)">
+                <graphics pos="(-450.00,130.00)" angle="0" flipping="0" hidden="1" tOrigin="(30.00,16.00)"/>
+            </item>
+        </CNTDLY>
+        <LUT id="60" regularShape="0" mode="0">
+            <item id="60" caption="3-bit LUT8 (MF2)">
+                <graphics pos="(-365.00,345.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <item id="61" caption="DFF/LATCH11 (MF2)">
+            <graphics pos="(-550.00,300.00)" angle="0" flipping="0" hidden="1" tOrigin="(25.00,10.00)"/>
+        </item>
+        <CNTDLY id="62" externalClockFrequence="0" externalClockFreqUnit="kHz">
+            <item id="62" caption="8-bit CNT2/DLY2 (MF2)">
+                <graphics pos="(-550.00,300.00)" angle="0" flipping="0" hidden="1" tOrigin="(30.00,16.00)"/>
+            </item>
+        </CNTDLY>
+        <LUT id="63" regularShape="0" mode="0">
+            <item id="63" caption="3-bit LUT9 (MF3)">
+                <graphics pos="(-365.00,505.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <item id="64" caption="DFF/LATCH12 (MF3)">
+            <graphics pos="(-730.00,460.00)" angle="0" flipping="0" hidden="1" tOrigin="(25.00,10.00)"/>
+        </item>
+        <CNTDLY id="65" externalClockFrequence="0" externalClockFreqUnit="kHz">
+            <item id="65" caption="8-bit CNT3/DLY3 (MF3)">
+                <graphics pos="(-730.00,460.00)" angle="0" flipping="0" hidden="1" tOrigin="(30.00,16.00)"/>
+            </item>
+        </CNTDLY>
+        <LUT id="66" regularShape="0" mode="0">
+            <item id="66" caption="3-bit LUT10 (MF4)">
+                <graphics pos="(565.00,15.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <item id="67" caption="DFF/LATCH13 (MF4)">
+            <graphics pos="(660.00,-210.00)" angle="0" flipping="0" hidden="1" tOrigin="(25.00,10.00)"/>
+        </item>
+        <CNTDLY id="68" externalClockFrequence="0" externalClockFreqUnit="kHz">
+            <item id="68" caption="8-bit CNT4/DLY4 (MF4)">
+                <graphics pos="(660.00,-210.00)" angle="0" flipping="0" hidden="1" tOrigin="(30.00,16.00)"/>
+            </item>
+        </CNTDLY>
+        <LUT id="69" regularShape="0" mode="0">
+            <item id="69" caption="3-bit LUT11 (MF5)">
+                <graphics pos="(565.00,175.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <item id="70" caption="DFF/LATCH14 (MF5)">
+            <graphics pos="(500.00,-50.00)" angle="0" flipping="0" hidden="1" tOrigin="(25.00,10.00)"/>
+        </item>
+        <CNTDLY id="71" externalClockFrequence="0" externalClockFreqUnit="kHz">
+            <item id="71" caption="8-bit CNT5/DLY5 (MF5)">
+                <graphics pos="(500.00,-50.00)" angle="0" flipping="0" hidden="1" tOrigin="(30.00,16.00)"/>
+            </item>
+        </CNTDLY>
+        <LUT id="72" regularShape="0" mode="0">
+            <item id="72" caption="3-bit LUT12 (MF6)">
+                <graphics pos="(565.00,335.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <item id="73" caption="DFF/LATCH15 (MF6)">
+            <graphics pos="(330.00,110.00)" angle="0" flipping="0" hidden="1" tOrigin="(25.00,10.00)"/>
+        </item>
+        <CNTDLY id="74" externalClockFrequence="0" externalClockFreqUnit="kHz">
+            <item id="74" caption="8-bit CNT6/DLY6 (MF6)">
+                <graphics pos="(330.00,110.00)" angle="0" flipping="0" hidden="1" tOrigin="(30.00,16.00)"/>
+            </item>
+        </CNTDLY>
+        <LUT id="75" regularShape="0" mode="0">
+            <item id="75" caption="3-bit LUT13 (MF7)">
+                <graphics pos="(565.00,495.00)" angle="90" flipping="0" hidden="1" tOrigin="(20.00,15.00)"/>
+            </item>
+        </LUT>
+        <item id="76" caption="DFF/LATCH16 (MF7)">
+            <graphics pos="(170.00,270.00)" angle="0" flipping="0" hidden="1" tOrigin="(25.00,10.00)"/>
+        </item>
+        <CNTDLY id="77" externalClockFrequence="0" externalClockFreqUnit="kHz">
+            <item id="77" caption="8-bit CNT7/DLY7 (MF7)">
+                <graphics pos="(170.00,270.00)" angle="0" flipping="0" hidden="1" tOrigin="(30.00,16.00)"/>
+            </item>
+        </CNTDLY>
+        <wire output="147" input="111" autoRouting="1" pen="#ff8000ff;1.00;1;32;128" lineType="2" protected="1" CWLid="0" wireText="NET0" wireState="0">
+            <points>(-702.00,682.00); (-695.00,682.00)</points>
+        </wire>
+        <wire output="148" input="112" autoRouting="1" pen="#ff8000ff;1.00;1;32;128" lineType="2" protected="1" CWLid="1" wireText="NET1" wireState="0">
+            <points>(-702.00,712.00); (-695.00,712.00)</points>
+        </wire>
+        <wire output="143" input="537" autoRouting="1" pen="#00ff00ff;1.00;1;32;128" lineType="1" protected="0" CWLid="10" wireText="NET10" wireState="0">
+            <points>(-189.00,520.00); (432.00,520.00); (432.00,360.00)</points>
+        </wire>
+        <wire output="143" input="553" autoRouting="1" pen="#00ff00ff;1.00;1;32;128" lineType="1" protected="0" CWLid="10" wireText="NET10" wireState="0">
+            <points>(-189.00,520.00); (45.00,520.00); (45.00,216.00)</points>
+        </wire>
+        <wire output="142" input="552" autoRouting="1" pen="#00ff00ff;1.00;1;32;128" lineType="1" protected="0" CWLid="8" wireText="NET8" wireState="0">
+            <points>(-186.00,294.00); (-13.00,294.00); (-13.00,182.00); (-2.00,182.00)</points>
+        </wire>
+        <wire output="157" input="621" autoRouting="1" pen="#00ff00ff;1.00;1;32;128" lineType="1" protected="0" CWLid="7" wireText="NET7" wireState="0">
+            <points>(-188.00,403.00); (105.00,403.00); (105.00,341.00); (120.00,341.00)</points>
+        </wire>
+        <wire output="142" input="622" autoRouting="1" pen="#00ff00ff;1.00;1;32;128" lineType="1" protected="0" CWLid="8" wireText="NET8" wireState="0">
+            <points>(-186.00,294.00); (105.00,294.00); (105.00,331.00); (120.00,331.00)</points>
+        </wire>
+        <wire output="162" input="623" autoRouting="1" pen="#00ff00ff;1.00;1;32;128" lineType="1" protected="0" CWLid="11" wireText="NET11" wireState="0">
+            <points>(89.00,186.00); (114.00,186.00); (114.00,321.00); (120.00,321.00)</points>
+        </wire>
+        <wire output="191" input="536" autoRouting="1" pen="#00ff00ff;1.00;1;32;128" lineType="1" protected="0" CWLid="9" wireText="NET9" wireState="0">
+            <points>(191.00,326.00); (388.00,326.00)</points>
+        </wire>
+        <wire output="144" input="624" autoRouting="1" pen="#00ff00ff;1.00;1;32;128" lineType="1" protected="0" CWLid="12" wireText="NET12" wireState="0">
+            <points>(-187.00,213.00); (-127.00,213.00); (-127.00,311.00); (120.00,311.00)</points>
+        </wire>
+        <wire output="84" input="551" autoRouting="1" pen="#00ff00ff;1.00;1;32;128" lineType="1" protected="0" CWLid="13" wireText="NET13" wireState="0">
+            <points>(-30.00,192.00); (-2.00,192.00)</points>
+        </wire>
+        <wire output="191" input="262" autoRouting="1" pen="#00ff00ff;1.00;1;32;128" lineType="1" protected="0" CWLid="9" wireText="NET9" wireState="0">
+            <points>(191.00,326.00); (213.00,326.00); (213.00,113.00); (-123.00,113.00); (-123.00,184.00); (-107.00,184.00)</points>
+        </wire>
+        <wire output="144" input="261" autoRouting="1" pen="#00ff00ff;1.00;1;32;128" lineType="1" protected="0" CWLid="12" wireText="NET12" wireState="0">
+            <points>(-187.00,213.00); (-126.00,213.00); (-126.00,200.00); (-107.00,200.00)</points>
+        </wire>
+    </chip>
+    <emulatorConfiguration version="1">
+        <settings>
+            <autoApply value="0"/>
+            <platformSelector id="3"/>
+        </settings>
+        <platform id="0" friendlyName="GreenPAK DIP Development Platform">
+            <lastConfiguration modified="0">
+                <SaveEmulator Name="Default">
+                    <PowerOptions synced="0"/>
+                    <PowerSwitch internalPower="1" externalPower="0"/>
+                    <ExpansionConnector></ExpansionConnector>
+                    <TestPoints/>
+                    <I2C slaveAddress="-1"/>
+                </SaveEmulator>
+            </lastConfiguration>
+            <savedConfigurations/>
+        </platform>
+        <platform id="1" friendlyName="GreenPAK Advanced Development Platform">
+            <lastConfiguration modified="0">
+                <SaveEmulator Name="Default">
+                    <PowerOptions synced="0"/>
+                    <PowerSwitch internalPower="1" externalPower="0"/>
+                    <ExpansionConnector></ExpansionConnector>
+                    <TestPoints/>
+                    <I2C slaveAddress="-1"/>
+                </SaveEmulator>
+            </lastConfiguration>
+            <savedConfigurations/>
+        </platform>
+        <platform id="2" friendlyName="GreenPAK Pro Development Platform">
+            <lastConfiguration modified="0">
+                <SaveEmulator Name="Default">
+                    <PowerOptions synced="0"/>
+                    <PowerSwitch internalPower="1" externalPower="0"/>
+                    <ExpansionConnector></ExpansionConnector>
+                    <TestPoints/>
+                    <I2C slaveAddress="-1"/>
+                </SaveEmulator>
+            </lastConfiguration>
+            <savedConfigurations/>
+        </platform>
+        <platform id="4" friendlyName="GreenPAK Serial Debugger">
+            <lastConfiguration modified="0">
+                <SaveEmulator Name="Default">
+                    <PowerOptions synced="0"/>
+                    <PowerSwitch internalPower="1" externalPower="0"/>
+                    <ExpansionConnector></ExpansionConnector>
+                    <TestPoints/>
+                    <I2C slaveAddress="-1"/>
+                </SaveEmulator>
+            </lastConfiguration>
+            <savedConfigurations/>
+        </platform>
+        <platform id="5" friendlyName="GreenPAK Advanced Development Platform w/ Logic Level Adapter #1">
+            <lastConfiguration modified="0">
+                <SaveEmulator Name="Default">
+                    <PowerOptions synced="0"/>
+                    <PowerSwitch internalPower="1" externalPower="0"/>
+                    <ExpansionConnector></ExpansionConnector>
+                    <TestPoints/>
+                    <I2C slaveAddress="-1"/>
+                </SaveEmulator>
+            </lastConfiguration>
+            <savedConfigurations/>
+        </platform>
+        <platform id="6" friendlyName="ForgeFPGA Development Platform">
+            <lastConfiguration modified="0">
+                <SaveEmulator Name="Default">
+                    <PowerOptions synced="0"/>
+                    <PowerSwitch internalPower="1" externalPower="0"/>
+                    <ExpansionConnector></ExpansionConnector>
+                    <TestPoints/>
+                    <I2C slaveAddress="-1"/>
+                </SaveEmulator>
+            </lastConfiguration>
+            <savedConfigurations/>
+        </platform>
+    </emulatorConfiguration>
+    <simulationConfiguration version="13" compatibleVersion="13" activePreset="-1">
+        <draftPreset>
+            <simulationPreset index="-1" name="Default" customGroupLayout="false" customParametricDcGroupLayout="false">
+                <transientAnalyses>
+                    <transientAnalysis stopTime="12m" maxTimeStep="20u" startTime="0" stepTime="20u" temperature="25" useInitialConditions="false" vdd="3.3"/>
+                </transientAnalyses>
+                <parametricDcAnalyses/>
+                <pinProbes>
+                    <pinProbe instrumentId="0" measurementFlags="1">
+                        <IdDto category="5" componentType="43" componentNumber="1" pinNumber="0"/>
+                    </pinProbe>
+                    <pinProbe instrumentId="1" measurementFlags="1">
+                        <IdDto category="5" componentType="43" componentNumber="2" pinNumber="0"/>
+                    </pinProbe>
+                </pinProbes>
+                <groups/>
+                <parametricDcGroups/>
+                <waveforms/>
+                <components>
+                    <component simulationModel="">
+                        <componentId category="5" componentType="0" componentNumber="1"/>
+                        <componentSpec manufacturer="" partNumber=""/>
+                        <componentUiSettings isFlippedHorizontally="false" isFlippedVertically="false" visible="true" position="(-818.116;93.379)" rotationState="0" sceneIndex="0"/>
+                        <schematicProps>
+                            <param id="name" affinity="4" value="GND"/>
+                        </schematicProps>
+                        <instanceProps/>
+                    </component>
+                    <component simulationModel="">
+                        <componentId category="5" componentType="0" componentNumber="2"/>
+                        <componentSpec manufacturer="" partNumber=""/>
+                        <componentUiSettings isFlippedHorizontally="false" isFlippedVertically="false" visible="true" position="(1005.89;233.679)" rotationState="0" sceneIndex="0"/>
+                        <schematicProps>
+                            <param id="name" affinity="4" value="GND"/>
+                        </schematicProps>
+                        <instanceProps/>
+                    </component>
+                    <component simulationModel="">
+                        <componentId category="5" componentType="0" componentNumber="3"/>
+                        <componentSpec manufacturer="" partNumber=""/>
+                        <componentUiSettings isFlippedHorizontally="false" isFlippedVertically="false" visible="true" position="(848.494;443.262)" rotationState="0" sceneIndex="0"/>
+                        <schematicProps>
+                            <param id="name" affinity="4" value="GND"/>
+                        </schematicProps>
+                        <instanceProps/>
+                    </component>
+                    <component simulationModel="">
+                        <componentId category="5" componentType="43" componentNumber="1"/>
+                        <componentSpec manufacturer="" partNumber=""/>
+                        <componentUiSettings isFlippedHorizontally="false" isFlippedVertically="false" visible="true" position="(-749.099;19.6022)" rotationState="0" sceneIndex="0"/>
+                        <schematicProps>
+                            <param id="name" affinity="4" value="V1"/>
+                            <param id="has_rc" affinity="1" value="1"/>
+                            <param id="int_r" affinity="3" value="10"/>
+                            <param id="int_c" affinity="3" value="100n"/>
+                            <param id="show_one_period" affinity="1" value="0"/>
+                            <param id="has_limited_voltage" affinity="1" value="0"/>
+                            <param id="dc_analysis_voltage" affinity="3" value="3.3"/>
+                            <param id="signal_type" affinity="1" value="1"/>
+                            <param id="prestart_delay" affinity="3" value="0"/>
+                            <param id="ramp_voltage" affinity="3" value="3.3"/>
+                            <param id="ramp_rise_time" affinity="3" value="1m"/>
+                        </schematicProps>
+                        <instanceProps/>
+                    </component>
+                    <component simulationModel="">
+                        <componentId category="5" componentType="43" componentNumber="2"/>
+                        <componentSpec manufacturer="" partNumber=""/>
+                        <componentUiSettings isFlippedHorizontally="true" isFlippedVertically="false" visible="true" position="(926.958;159.848)" rotationState="0" sceneIndex="0"/>
+                        <schematicProps>
+                            <param id="name" affinity="4" value="V2"/>
+                            <param id="has_rc" affinity="1" value="1"/>
+                            <param id="int_r" affinity="3" value="10"/>
+                            <param id="int_c" affinity="3" value="100n"/>
+                            <param id="show_one_period" affinity="1" value="0"/>
+                            <param id="has_limited_voltage" affinity="1" value="1"/>
+                            <param id="dc_analysis_voltage" affinity="3" value="3.3"/>
+                            <param id="signal_type" affinity="1" value="1"/>
+                            <param id="prestart_delay" affinity="3" value="0"/>
+                            <param id="ramp_voltage" affinity="3" value="3.3"/>
+                            <param id="ramp_rise_time" affinity="3" value="1m"/>
+                        </schematicProps>
+                        <instanceProps/>
+                    </component>
+                </components>
+                <networks>
+                    <network networkId="6" name="NET6">
+                        <connections>
+                            <connection connectionType="0" routingType="0" sceneIndex="0">
+                                <from>
+                                    <IdDto category="5" componentType="43" componentNumber="1" pinNumber="1"/>
+                                </from>
+                                <to>
+                                    <IdDto category="5" componentType="0" componentNumber="1" pinNumber="0"/>
+                                </to>
+                                <path>
+                                    <point value="(-771;41)"/>
+                                    <point value="(-802;41)"/>
+                                    <point value="(-802;71)"/>
+                                </path>
+                            </connection>
+                        </connections>
+                    </network>
+                    <network networkId="5" name="NET5">
+                        <connections>
+                            <connection connectionType="0" routingType="0" sceneIndex="0">
+                                <from>
+                                    <IdDto category="5" componentType="0" componentNumber="3" pinNumber="0"/>
+                                </from>
+                                <to>
+                                    <IdDto category="100" componentType="45" componentNumber="0" pinNumber="10"/>
+                                </to>
+                                <path>
+                                    <point value="(864;421)"/>
+                                    <point value="(864;391)"/>
+                                    <point value="(832;391)"/>
+                                </path>
+                            </connection>
+                        </connections>
+                    </network>
+                    <network networkId="4" name="NET4">
+                        <connections>
+                            <connection connectionType="0" routingType="0" sceneIndex="0">
+                                <from>
+                                    <IdDto category="5" componentType="43" componentNumber="2" pinNumber="0"/>
+                                </from>
+                                <to>
+                                    <IdDto category="100" componentType="45" componentNumber="0" pinNumber="7"/>
+                                </to>
+                                <path>
+                                    <point value="(905;181)"/>
+                                    <point value="(832;181)"/>
+                                </path>
+                            </connection>
+                        </connections>
+                    </network>
+                    <network networkId="3" name="NET3">
+                        <connections>
+                            <connection connectionType="0" routingType="0" sceneIndex="0">
+                                <from>
+                                    <IdDto category="5" componentType="0" componentNumber="2" pinNumber="0"/>
+                                </from>
+                                <to>
+                                    <IdDto category="5" componentType="43" componentNumber="2" pinNumber="1"/>
+                                </to>
+                                <path>
+                                    <point value="(1022;212)"/>
+                                    <point value="(1022;181)"/>
+                                    <point value="(990;181)"/>
+                                </path>
+                            </connection>
+                        </connections>
+                    </network>
+                    <network networkId="2" name="NET2">
+                        <connections>
+                            <connection connectionType="0" routingType="0" sceneIndex="0">
+                                <from>
+                                    <IdDto category="5" componentType="43" componentNumber="1" pinNumber="0"/>
+                                </from>
+                                <to>
+                                    <IdDto category="100" componentType="45" componentNumber="0" pinNumber="20"/>
+                                </to>
+                                <path>
+                                    <point value="(-686;41)"/>
+                                    <point value="(-613;41)"/>
+                                </path>
+                            </connection>
+                        </connections>
+                    </network>
+                </networks>
+            </simulationPreset>
+        </draftPreset>
+        <simulationPresets/>
+    </simulationConfiguration>
+    <projectData>
+        <specs>
+            <lastModify lastModifyValue="01.02.2022 7:10:58"/>
+            <vddSpecs vddMin="2.8" vddTyp="3" vddMax="3.2"/>
+            <vdd2Specs vdd2Min="2.8" vdd2Typ="3" vdd2Max="3.2"/>
+            <tempSpecs tempMin="0" tempTyp="25" tempMax="40"/>
+        </specs>
+        <projectDataFields>
+            <textLineDataField name="Customer&#10;Name:" id="2" text=""/>
+            <textLineDataField name="Customer&#10;Project Name:" id="3" text=""/>
+            <textLineDataField name="Customer&#10;Project Number:" id="4" text=""/>
+            <textLineDataField name="Customer&#10;Version Number:" id="5" text=""/>
+            <multiTextLineDataField>
+                <textLineDataField name="Notes:" id="6" text=""/>
+            </multiTextLineDataField>
+        </projectDataFields>
+    </projectData>
+    <i2cTool>
+        <i2cDebugger version="1"/>
+        <i2cStepper version="1" rowCount="0"/>
+        <i2cRegsTable version="1" tabCount="0"/>
+    </i2cTool>
+</GPDProject>

+ 17 - 0
greenpak/SCSI_Accelerator_SLG46824.hex

@@ -0,0 +1,17 @@
+:10000000C40900000000000038C200000000000029
+:10001000000000000000D0081301000000000000F4
+:1000200000000000000000000000000000000000D0
+:1000300000000000000000000000000000000000C0
+:100040000000E700000000000000000000000000C9
+:1000500000000000000000000000000000000000A0
+:100060000030700030203030000030303000303050
+:100070003030000000000000000000000000000020
+:1000800000000000001422300C00000000000000FE
+:10009000060000000000000000004000000000001A
+:1000A00000D714200001000000020100000200013E
+:1000B0000000020100000200010000020100000235
+:1000C0000001000002000100000001010100000029
+:1000D0000000000000000000000000000000000020
+:1000E0000000000000000000000000000000000010
+:1000F000000000000000000000000000000000A55B
+:00000001FF

BIN
greenpak/SCSI_Accelerator_SLG46824.png


+ 158 - 0
lib/SCSI2SD/CHANGELOG

@@ -0,0 +1,158 @@
+2022xxxx        6.4.14
+    - Fix firmware version displaying as "0.0" in scsi2sd-util when there is no
+    SD card inserted.
+    - Reduce some delays for slight performance improvements
+    - Use SD High-Speed mode on V6 2021 hardware.
+
+20220121        6.4.13
+    - Fix SCSI writes with sector sizes larger than 512.
+    - Fix 2Gb SD cards being detected as 1Gb
+    - Fix for CD emulation stopping the drive when receiving a load/eject
+    request
+
+20210810        6.4.12
+    - Fix USB disconnect issue when no SD card is installed.
+
+20210628        6.4.11
+    - Remove the "Blind Writes" option from scsi2sd-util. The firmware no longer
+    requires this option for improved write performance
+    - Fix firmware hang if there was no activity for a few minutes
+
+20210508        6.4.4
+    - More bug fixes for firmware hanging during read/writes
+
+20210504        6.4.3
+    - More bug fixes while writing data.
+
+20210503        6.4.2
+    - Bug fix for errors while writing data
+    - Fix for scsi2sd-util6 under Windows which may fail to detect a board without a SD card inserted.
+
+20210426		6.4.1
+    - 2021 hardware support release.
+    - scsi2sd-util6 stability improvements (contributed by Jonathan Wakely.
+
+20201012		6.3.2
+	- Increase limit of READ/WRITE BUFFER command for improved compatibility
+	SGI Iris hosts
+	- Add new config option to enable blind writes to improve SD card write
+	performance. This was previously enabled default but causes problems
+	with some SCSI hosts.
+
+20200419		6.3.1
+	- Added checks to ensure the correct firmware version is loaded.
+	V6 Rev.f and older boards need the "firmware.V6.revF.dfu" firmware updates.
+	V6 2020c and newer boards need the "firmware.V6.2020.dfu" firmware updates.
+
+20200216		6.3.0
+	- Breaking change: Firmware updates on windows now require the UsbDK
+	driver to be installed.
+
+	- Fix regression preventing some machines from booting
+	- Update libusb to 1.0.23 for dfu-util
+	- Modify dfu-util to use the UsbDk driver via libusb.
+
+20200130		6.2.15
+	- Fix issue writing more than 512kb of data in one write command
+	(bug introduced 6.2.7)
+	- Fix possible data corruption bug when reading or writing more than
+	64kb per command (fixed in most cases by 6.2.14)
+
+20200101		6.2.14
+	- Fix for invalid CDROM READ TOC responses (Thanks Simon Gander)
+	- Fix for data corruption for hosts that transfer more than 64k per
+	write.
+
+20191208		6.2.9
+	- Fix to prevent sending floppy geometry mode page when not configured as
+	a floppy (Thanks Landon Rodgers)
+	- Fix for VMS 5.5-2 Inquiry allocation lengths. Requires setting "vms" quirk
+	mode in the XML config (Thanks Landon Rodgers)
+
+20191030		6.2.8
+	- Fix incorrect results from the self-test function.
+
+20191009		6.2.7
+	- Slight improvements to data throughput, which may assist SCSI hosts with
+	short timeouts.
+
+20190529		6.2.5
+	- Add scsi mode page 0 support
+	- Fix SD card hotswap bug when the SCSI host is constantly polling
+
+20190502		6.2.4 (Beta)
+	- Port XEBEC support from v5 firmware
+	- Add Flexible Disk Drive Geometry SCSI MODE page
+	- Stability improvements
+	- Fix regression from 6.1.3 firmware for Kurzweil K2000
+
+20181011		6.2.1
+	- Fix bug in USB disk interface with disks over 4GB
+
+20180926        6.2.0
+	- Fix bug with non-512 byte sectors.
+	- Fix bug when writing with multiple SCSI devices on the chain
+	- Performance improvements to write speeds.
+
+20180430		6.1.4
+	- Fix bug in self-test function
+
+20180131		6.1.3
+	- Fix bug that caused stability issues with 10MB/s transfers.
+
+20171128		6.1.2
+	- Fix synchronous negotiation bugs
+
+20170520		6.1.1
+	- Performance improvements to improve throughput at all scsi speeds
+	- Add new "turbo" speed option to boost speeds.
+		- May not be reliable, and use is not supported.
+		- Async timings trimmed
+		- Sync speeds boosted to theoretical 15.625MB/s, with 12.0MB/s measured
+		read througput.
+		- SD card put in "high speed" mode.
+		- USB for configuration/firmware updates is disabled in turbo mode when
+		processing SCSI commands. A power cycle may be required to connect
+		via USB to reset the 48MHz clock back to 48MHz.
+	- Fix scsi2sd-util6 size and sector-size inputs
+	- Fix crash when configured scsi disk starting sector is less than
+	SD card size
+
+20170329		6.1.0
+	- Enable synchronous transfers on SCSI1 hosts
+	- Support 4MB/s sync transfers for Amiga A590 (WD33C93)
+	- Merge v4.7 release changes, excluding custom mode/inquiry pages
+	- various bug fixes
+
+20161006		6.0.13
+	- Fixed SCSI timing issue
+	- Added glitch filter on SCSI signals.
+	- Re-implemented SCSI parity checking.
+
+20160912		6.0.10
+	- Fixed write issue with UHS-I Speed Class 3 SD cards.
+	- More stability bug fixes
+
+20160827		6.0.8
+	- Fixed "protocol error" issues when saving configuration to SD cards.
+	- Synchronous transfers supported ! 5MB/s and 10MB/s supported.
+	- Fix for accessing data via USB with more than 2 devices configured.
+
+20160815		6.0.6
+	- Fix performance bugs
+
+20160814		6.05
+	- More SCSI bug fixes (some timing issues resolved in the FPGA image)
+	- Firmware update support using scsi2sd-util6.
+
+20160716		6.03 (BETA3)
+	- SCSI bug fixes.
+
+20160616		6.01
+	- Improved SD card compatibility
+	- Fixed SCSI interfaces on slower SCSI controllers
+	- Significant performance improvements
+	- Added SD card hotswap support.
+
+20160528		6.0
+	- First BETA firmware for the 6.0 hardware version of the SCSI2SD.

+ 63 - 0
lib/SCSI2SD/COPYING

@@ -0,0 +1,63 @@
+SCSI2SD is distributed under the GNU General Public License version 3. Please
+find the license in the file gplv3.txt
+
+The additional license terms apply:
+
+////////////////////////////////////////////////////////////////////////////////////
+Code generated via STM32CubeMX:
+
+COPYRIGHT(c) 2015 STMicroelectronics
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+  1. Redistributions of source code must retain the above copyright notice,
+     this list of conditions and the following disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright notice,
+     this list of conditions and the following disclaimer in the documentation
+     and/or other materials provided with the distribution.
+  3. Neither the name of STMicroelectronics nor the names of its contributors
+     may be used to endorse or promote products derived from this software
+     without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+////////////////////////////////////////////////////////////////////////////////////
+ARM CMSIS, distributed with STM32CubeMX:
+
+Copyright (c) 2009 - 2015 ARM LIMITED
+
+All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+- Neither the name of ARM nor the names of its contributors may be used
+  to endorse or promote products derived from this software without
+  specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+

+ 674 - 0
lib/SCSI2SD/gplv3.txt

@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.

+ 252 - 0
lib/SCSI2SD/include/scsi2sd.h

@@ -0,0 +1,252 @@
+//	Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef scsi2sd_h
+#define scsi2sd_h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Common type definitions shared between the firmware and config tools
+
+	The configuration data is now stored on the SD card, occupying the
+	last 2 sectors.
+
+	BoardConfig
+	TargetConfig (disk 0)
+	TargetConfig (disk 1)
+	TargetConfig (disk 2)
+	TargetConfig (disk 3)
+	TargetConfig (disk 4)
+	TargetConfig (disk 5)
+	TargetConfig (disk 6)
+
+*/
+
+#include "stdint.h"
+
+#define S2S_MAX_TARGETS 8
+#define S2S_CFG_SIZE (S2S_MAX_TARGETS * sizeof(S2S_TargetCfg) + sizeof(S2S_BoardCfg))
+
+typedef enum
+{
+	S2S_CFG_TARGET_ID_BITS = 0x07,
+	S2S_CFG_TARGET_ENABLED = 0x80
+} S2S_CFG_TARGET_FLAGS;
+
+typedef enum
+{
+	S2S_CFG_ENABLE_UNIT_ATTENTION = 1,
+	S2S_CFG_ENABLE_PARITY = 2,
+	S2S_CFG_ENABLE_SCSI2 = 4,
+	S2S_CFG_DISABLE_GLITCH = 8,
+	S2S_CFG_ENABLE_CACHE = 16,
+	S2S_CFG_ENABLE_DISCONNECT = 32,
+	S2S_CFG_ENABLE_SEL_LATCH = 64,
+	S2S_CFG_MAP_LUNS_TO_IDS = 128
+} S2S_CFG_FLAGS;
+
+typedef enum
+{
+	S2S_CFG_ENABLE_TERMINATOR = 1
+	//S2S_CFG_ENABLE_BLIND_WRITES = 2, // Obosolete
+} S2S_CFG_FLAGS6;
+
+typedef enum
+{
+	S2S_CFG_FIXED,
+	S2S_CFG_REMOVEABLE,
+	S2S_CFG_OPTICAL,
+	S2S_CFG_FLOPPY_14MB,
+	S2S_CFG_MO,
+	S2S_CFG_SEQUENTIAL
+
+} S2S_CFG_TYPE;
+
+typedef enum
+{
+	S2S_CFG_QUIRKS_NONE = 0,
+	S2S_CFG_QUIRKS_APPLE = 1,
+	S2S_CFG_QUIRKS_OMTI = 2,
+	S2S_CFG_QUIRKS_XEBEC = 4,
+	S2S_CFG_QUIRKS_VMS = 8
+} S2S_CFG_QUIRKS;
+
+typedef enum
+{
+	S2S_CFG_SPEED_NoLimit,
+	S2S_CFG_SPEED_ASYNC_15,
+	S2S_CFG_SPEED_ASYNC_33,
+	S2S_CFG_SPEED_ASYNC_50,
+	S2S_CFG_SPEED_SYNC_5,
+	S2S_CFG_SPEED_SYNC_10,
+	S2S_CFG_SPEED_TURBO
+} S2S_CFG_SPEED;
+
+typedef struct __attribute__((packed))
+{
+	// bits 7 -> 3 = S2S_CFG_TARGET_FLAGS
+	// bits 2 -> 0 = target SCSI ID.
+	uint8_t scsiId;
+
+	uint8_t deviceType; // S2S_CFG_TYPE
+	uint8_t flagsDEPRECATED; // S2S_CFG_FLAGS, removed in v4.5
+	uint8_t deviceTypeModifier; // Used in INQUIRY response.
+
+	uint32_t sdSectorStart;
+	uint32_t scsiSectors;
+
+	uint16_t bytesPerSector;
+
+	// Max allowed by legacy IBM-PC bios is 6 bits (63)
+	uint16_t sectorsPerTrack;
+
+	// MS-Dos up to 7.10 will crash on >= 256 heads.
+	uint16_t headsPerCylinder;
+
+
+	char vendor[8];
+	char prodId[16];
+	char revision[4];
+	char serial[16];
+
+	uint16_t quirks; // S2S_CFG_QUIRKS
+
+	uint8_t reserved[64]; // Pad out to 128 bytes for main section.
+} S2S_TargetCfg;
+
+typedef struct __attribute__((packed))
+{
+	char magic[4]; // 'BCFG'
+	uint8_t flags; // S2S_CFG_FLAGS
+	uint8_t startupDelay; // Seconds.
+	uint8_t selectionDelay; // milliseconds. 255 = auto
+	uint8_t flags6; // S2S_CFG_FLAGS6
+
+	uint8_t scsiSpeed;
+
+	uint8_t reserved[119]; // Pad out to 128 bytes
+} S2S_BoardCfg;
+
+typedef enum
+{
+	S2S_CMD_NONE, // Invalid
+
+	// Command content:
+	// uint8_t S2S_CFG_PING
+	// Response:
+	// S2S_CFG_STATUS
+	S2S_CMD_PING,
+
+	// Command content:
+	// uint8_t S2S_CFG_WRITEFLASH
+	// uint8_t[256] flashData
+	// uint8_t flashArray
+	// uint8_t flashRow
+	// Response:
+	// S2S_CFG_STATUS
+	S2S_CMD_WRITEFLASH,
+
+	// Command content:
+	// uint8_t S2S_CFG_READFLASH
+	// uint8_t flashArray
+	// uint8_t flashRow
+	// Response:
+	// 256 bytes of flash
+	S2S_CMD_READFLASH,
+
+	// Command content:
+	// uint8_t S2S_CFG_REBOOT
+	// Response: None.
+	S2S_CMD_REBOOT,
+
+	// Command content:
+	// uint8_t S2S_CFG_INFO
+	// Response:
+	// uint8_t[16] CSD
+	// uint8_t[16] CID
+	S2S_CMD_SDINFO,
+
+	// Command content:
+	// uint8_t S2S_CFG_SCSITEST
+	// Response:
+	// S2S_CFG_STATUS
+	// uint8_t result code (0 = passed)
+	S2S_CMD_SCSITEST,
+
+	// Command content:
+	// uint8_t S2S_CFG_DEVINFO
+	// Response:
+	// uint16_t protocol version (MSB)
+	// uint16_t firmware version (MSB)
+	// uint32_t SD capacity(MSB)
+	S2S_CMD_DEVINFO,
+
+	// Command content:
+	// uint8_t S2S_CFG_SD_WRITE
+	// uint32_t Sector Number (MSB)
+	// uint8_t[512] data
+	// Response:
+	// S2S_CFG_STATUS
+	S2S_CMD_SD_WRITE,
+
+	// Command content:
+	// uint8_t S2S_CFG_SD_READ
+	// uint32_t Sector Number (MSB)
+	// Response:
+	// 512 bytes of data
+	S2S_CMD_SD_READ,
+
+	// Command content:
+	// uint8_t S2S_CFG_DEBUG
+	// Response:
+	S2S_CMD_DEBUG,
+} S2S_COMMAND;
+
+typedef enum
+{
+	S2S_CFG_STATUS_GOOD,
+	S2S_CFG_STATUS_ERR,
+	S2S_CFG_STATUS_BUSY
+} S2S_CFG_STATUS;
+
+
+
+
+#ifdef __cplusplus
+} // extern "C"
+
+	#include <type_traits>
+	static_assert(
+		std::is_pod<S2S_TargetCfg>::value, "Misuse of TargetConfig struct"
+		);
+	static_assert(
+		sizeof(S2S_TargetCfg) == 128,
+		"TargetConfig struct size mismatch"
+		);
+
+	static_assert(
+		std::is_pod<S2S_BoardCfg>::value, "Misuse of BoardConfig struct"
+		);
+	static_assert(
+		sizeof(S2S_BoardCfg) == 128,
+		"BoardConfig struct size mismatch"
+		);
+
+#endif
+
+#endif

+ 13 - 0
lib/SCSI2SD/library.json

@@ -0,0 +1,13 @@
+{
+    "name": "SCSI2SD",
+    "version": "6.4.13",
+    "repository": { "type": "git", "url": "git://www.codesrc.com/git/SCSI2SD-V6" },
+    "authors": [{ "name": "Michael McMaster", "email": "michael@codesrc.com" }],
+    "license": "GPL-3.0-or-later",
+    "homepage": "http://www.codesrc.com/mediawiki/index.php/SCSI2SD",
+    "frameworks": "*",
+    "platforms": "*",
+    "build": {
+        "flags": ["-Iinclude", "-Isrc/firmware"]
+    }
+}

+ 324 - 0
lib/SCSI2SD/src/firmware/cdrom.c

@@ -0,0 +1,324 @@
+//	Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "config.h"
+#include "cdrom.h"
+
+#include <string.h>
+
+static const uint8_t SimpleTOC[] =
+{
+	0x00, // toc length, MSB
+	0x12, // toc length, LSB
+	0x01, // First track number
+	0x01, // Last track number,
+	// TRACK 1 Descriptor
+	0x00, // reserved
+	0x14, // Q sub-channel encodes current position, Digital track
+	0x01, // Track 1,
+	0x00, // Reserved
+	0x00,0x00,0x00,0x00, // Track start sector (LBA)
+	0x00, // reserved
+	0x14, // Q sub-channel encodes current position, Digital track
+	0xAA, // Leadout Track
+	0x00, // Reserved
+	0x00,0x00,0x00,0x00, // Track start sector (LBA)
+};
+
+static const uint8_t SessionTOC[] =
+{
+	0x00, // toc length, MSB
+	0x0A, // toc length, LSB
+	0x01, // First session number
+	0x01, // Last session number,
+	// TRACK 1 Descriptor
+	0x00, // reserved
+	0x14, // Q sub-channel encodes current position, Digital track
+	0x01, // First track number in last complete session
+	0x00, // Reserved
+	0x00,0x00,0x00,0x00 // LBA of first track in last session
+};
+
+
+static const uint8_t FullTOC[] =
+{
+	0x00, // toc length, MSB
+	0x44, // toc length, LSB
+	0x01, // First session number
+	0x01, // Last session number,
+	// A0 Descriptor
+	0x01, // session number
+	0x14, // ADR/Control
+	0x00, // TNO
+	0xA0, // POINT
+	0x00, // Min
+	0x00, // Sec
+	0x00, // Frame
+	0x00, // Zero
+	0x01, // First Track number.
+	0x00, // Disc type 00 = Mode 1
+	0x00,  // PFRAME
+	// A1
+	0x01, // session number
+	0x14, // ADR/Control
+	0x00, // TNO
+	0xA1, // POINT
+	0x00, // Min
+	0x00, // Sec
+	0x00, // Frame
+	0x00, // Zero
+	0x01, // Last Track number
+	0x00, // PSEC
+	0x00,  // PFRAME
+	// A2
+	0x01, // session number
+	0x14, // ADR/Control
+	0x00, // TNO
+	0xA2, // POINT
+	0x00, // Min
+	0x00, // Sec
+	0x00, // Frame
+	0x00, // Zero
+	0x79, // LEADOUT position BCD
+	0x59, // leadout PSEC BCD
+	0x74, // leadout PFRAME BCD
+	// TRACK 1 Descriptor
+	0x01, // session number
+	0x14, // ADR/Control
+	0x00, // TNO
+	0x01, // Point
+	0x00, // Min
+	0x00, // Sec
+	0x00, // Frame
+	0x00, // Zero
+	0x00, // PMIN
+	0x00, // PSEC
+	0x00,  // PFRAME
+	// b0
+	0x01, // session number
+	0x54, // ADR/Control
+	0x00, // TNO
+	0xB1, // POINT
+	0x79, // Min BCD
+	0x59, // Sec BCD
+	0x74, // Frame BCD
+	0x00, // Zero
+	0x79, // PMIN BCD
+	0x59, // PSEC BCD
+	0x74,  // PFRAME BCD
+	// c0
+	0x01, // session number
+	0x54, // ADR/Control
+	0x00, // TNO
+	0xC0, // POINT
+	0x00, // Min
+	0x00, // Sec
+	0x00, // Frame
+	0x00, // Zero
+	0x00, // PMIN
+	0x00, // PSEC
+	0x00  // PFRAME
+};
+
+static void LBA2MSF(uint32_t LBA, uint8_t* MSF)
+{
+	MSF[0] = 0; // reserved.
+	MSF[3] = LBA % 75; // M
+	uint32_t rem = LBA / 75;
+
+	MSF[2] = rem % 60; // S
+	MSF[1] = rem / 60;
+
+}
+
+static void doReadTOC(int MSF, uint8_t track, uint16_t allocationLength)
+{
+	// We only support track 1.
+	// track 0 means "return all tracks"
+	if (track > 1)
+	{
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+	else
+	{
+		uint32_t len = sizeof(SimpleTOC);
+		memcpy(scsiDev.data, SimpleTOC, len);
+
+		uint32_t capacity = getScsiCapacity(
+			scsiDev.target->cfg->sdSectorStart,
+			scsiDev.target->liveCfg.bytesPerSector,
+			scsiDev.target->cfg->scsiSectors);
+
+		// Replace start of leadout track
+		if (MSF)
+		{
+			LBA2MSF(capacity, scsiDev.data + 0x10);
+		}
+		else
+		{
+			scsiDev.data[0x10] = capacity >> 24;
+			scsiDev.data[0x11] = capacity >> 16;
+			scsiDev.data[0x12] = capacity >> 8;
+			scsiDev.data[0x13] = capacity;
+		}
+
+		if (len > allocationLength)
+		{
+			len = allocationLength;
+		}
+		scsiDev.dataLen = len;
+		scsiDev.phase = DATA_IN;
+	}
+}
+
+static void doReadSessionInfo(uint8_t session, uint16_t allocationLength)
+{
+	uint32_t len = sizeof(SessionTOC);
+	memcpy(scsiDev.data, SessionTOC, len);
+
+	if (len > allocationLength)
+	{
+		len = allocationLength;
+	}
+	scsiDev.dataLen = len;
+	scsiDev.phase = DATA_IN;
+}
+
+static inline uint8_t
+fromBCD(uint8_t val)
+{
+	return ((val >> 4) * 10) + (val & 0xF);
+}
+
+static void doReadFullTOC(int convertBCD, uint8_t session, uint16_t allocationLength)
+{
+	// We only support session 1.
+	if (session > 1)
+	{
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+	else
+	{
+		uint32_t len = sizeof(FullTOC);
+		memcpy(scsiDev.data, FullTOC, len);
+
+		if (convertBCD)
+		{
+			int descriptor = 4;
+			while (descriptor < len)
+			{
+				int i;
+				for (i = 0; i < 7; ++i)
+				{
+					scsiDev.data[descriptor + i] =
+						fromBCD(scsiDev.data[descriptor + 4 + i]);
+				}
+				descriptor += 11;
+			}
+
+		}
+
+		if (len > allocationLength)
+		{
+			len = allocationLength;
+		}
+		scsiDev.dataLen = len;
+		scsiDev.phase = DATA_IN;
+	}
+}
+
+static uint8_t SimpleHeader[] =
+{
+	0x01, // 2048byte user data, L-EC in 288 byte aux field.
+	0x00, // reserved
+	0x00, // reserved
+	0x00, // reserved
+	0x00,0x00,0x00,0x00 // Track start sector (LBA or MSF)
+};
+
+void doReadHeader(int MSF, uint32_t lba, uint16_t allocationLength)
+{
+	uint32_t len = sizeof(SimpleHeader);
+	memcpy(scsiDev.data, SimpleHeader, len);
+	if (len > allocationLength)
+	{
+		len = allocationLength;
+	}
+	scsiDev.dataLen = len;
+	scsiDev.phase = DATA_IN;
+}
+
+
+// Handle direct-access scsi device commands
+int scsiCDRomCommand()
+{
+	int commandHandled = 1;
+
+	uint8_t command = scsiDev.cdb[0];
+	if (command == 0x43)
+	{
+		// CD-ROM Read TOC
+		int MSF = scsiDev.cdb[1] & 0x02 ? 1 : 0;
+		uint8_t track = scsiDev.cdb[6];
+		uint16_t allocationLength =
+			(((uint32_t) scsiDev.cdb[7]) << 8) +
+			scsiDev.cdb[8];
+
+		// Reject MMC commands for now, otherwise the TOC data format
+		// won't be understood.
+		// The "format" field is reserved for SCSI-2
+		uint8_t format = scsiDev.cdb[2] & 0x0F;
+		switch (format)
+		{
+			case 0: doReadTOC(MSF, track, allocationLength); break; // SCSI-2
+			case 1: doReadSessionInfo(MSF, allocationLength); break; // MMC2
+			case 2: doReadFullTOC(0, track, allocationLength); break; // MMC2
+			case 3: doReadFullTOC(1, track, allocationLength); break; // MMC2
+			default:
+			{
+				scsiDev.status = CHECK_CONDITION;
+				scsiDev.target->sense.code = ILLEGAL_REQUEST;
+				scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+				scsiDev.phase = STATUS;
+			}
+		}
+	}
+	else if (command == 0x44)
+	{
+		// CD-ROM Read Header
+		int MSF = scsiDev.cdb[1] & 0x02 ? 1 : 0;
+		uint32_t lba = 0; // IGNORED for now
+		uint16_t allocationLength =
+			(((uint32_t) scsiDev.cdb[7]) << 8) +
+			scsiDev.cdb[8];
+		doReadHeader(MSF, lba, allocationLength);
+	}
+	else
+	{
+		commandHandled = 0;
+	}
+
+	return commandHandled;
+}
+

+ 22 - 0
lib/SCSI2SD/src/firmware/cdrom.h

@@ -0,0 +1,22 @@
+//	Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef CDROM_H
+#define CDROM_H
+
+int scsiCDRomCommand(void);
+
+#endif

+ 30 - 0
lib/SCSI2SD/src/firmware/config.h

@@ -0,0 +1,30 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef S2S_Config_H
+#define S2S_Config_H
+
+#include "scsi2sd.h"
+
+void s2s_configInit(S2S_BoardCfg* config);
+void s2s_debugInit(void);
+void s2s_configPoll(void);
+void s2s_configSave(int scsiId, uint16_t byesPerSector);
+
+const S2S_TargetCfg* s2s_getConfigByIndex(int index);
+const S2S_TargetCfg* s2s_getConfigById(int scsiId);
+
+#endif

+ 247 - 0
lib/SCSI2SD/src/firmware/diagnostic.c

@@ -0,0 +1,247 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "diagnostic.h"
+
+#include <string.h>
+
+static const uint8_t SupportedDiagnosticPages[] =
+{
+0x00, // Page Code
+0x00, // Reserved
+0x02, // Page length
+0x00, // Support "Supported diagnostic page"
+0x40  // Support "Translate address page"
+};
+
+void scsiSendDiagnostic()
+{
+	// SEND DIAGNOSTIC
+	// Pretend to do self-test. Actual data is returned via the
+	// RECEIVE DIAGNOSTIC RESULTS command.
+	int selfTest = scsiDev.cdb[1] & 0x04;
+	uint32_t paramLength =
+		(((uint32_t) scsiDev.cdb[3]) << 8) +
+		scsiDev.cdb[4];
+
+	if (!selfTest)
+	{
+		// Initiator sends us page data.
+		scsiDev.dataLen = paramLength;
+		scsiDev.phase = DATA_OUT;
+
+		if (scsiDev.dataLen > sizeof (scsiDev.data))
+		{
+			// Nowhere to store this data!
+			// Shouldn't happen - our buffer should be many magnitudes larger
+			// than the required size for diagnostic parameters.
+			scsiDev.target->sense.code = ILLEGAL_REQUEST;
+			scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+			scsiDev.status = CHECK_CONDITION;
+			scsiDev.phase = STATUS;
+		}
+	}
+	else
+	{
+		// Default command result will be a status of GOOD anyway.
+	}
+}
+
+void scsiReceiveDiagnostic()
+{
+	// RECEIVE DIAGNOSTIC RESULTS
+	// We assume scsiDev.data contains the contents of a previous
+	// SEND DIAGNOSTICS command.  We only care about the page-code part
+	// of the parameter list.
+	uint8_t pageCode = scsiDev.data[0];
+
+	int allocLength =
+		(((uint16_t) scsiDev.cdb[3]) << 8) +
+		scsiDev.cdb[4];
+
+
+	if (pageCode == 0x00)
+	{
+		memcpy(
+			scsiDev.data,
+			SupportedDiagnosticPages,
+			sizeof(SupportedDiagnosticPages));
+		scsiDev.dataLen = sizeof(SupportedDiagnosticPages);
+		scsiDev.phase = DATA_IN;
+	}
+	else if (pageCode == 0x40)
+	{
+		// Translate between logical block address, physical sector address, or
+		// physical bytes.
+		uint8_t suppliedFmt = scsiDev.data[4] & 0x7;
+		uint8_t translateFmt = scsiDev.data[5] & 0x7;
+
+		// Convert each supplied address back to a simple
+		// 64bit linear address, then convert back again.
+		uint64_t fromByteAddr =
+			scsiByteAddress(
+				scsiDev.target->liveCfg.bytesPerSector,
+				scsiDev.target->cfg->headsPerCylinder,
+				scsiDev.target->cfg->sectorsPerTrack,
+				suppliedFmt,
+				&scsiDev.data[6]);
+
+		scsiSaveByteAddress(
+			scsiDev.target->liveCfg.bytesPerSector,
+			scsiDev.target->cfg->headsPerCylinder,
+			scsiDev.target->cfg->sectorsPerTrack,
+			translateFmt,
+			fromByteAddr,
+			&scsiDev.data[6]);
+
+		// Fill out the rest of the response.
+		// (Clear out any optional bits).
+		scsiDev.data[4] = suppliedFmt;
+		scsiDev.data[5] = translateFmt;
+
+		scsiDev.dataLen = 14;
+		scsiDev.phase = DATA_IN;
+	}
+	else
+	{
+		// error.
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+
+	if (scsiDev.phase == DATA_IN && scsiDev.dataLen > allocLength)
+	{
+		// simply truncate the response.
+		scsiDev.dataLen = allocLength;
+	}
+
+	{
+		// Set the first byte to indicate LUN presence.
+		if (scsiDev.lun) // We only support lun 0
+		{
+			scsiDev.data[0] = 0x7F;
+		}
+	}
+}
+
+void scsiReadBuffer()
+{
+	// READ BUFFER
+	// Used for testing the speed of the SCSI interface.
+	uint8_t mode = scsiDev.data[1] & 7;
+
+	int allocLength =
+		(((uint32_t) scsiDev.cdb[6]) << 16) +
+		(((uint32_t) scsiDev.cdb[7]) << 8) +
+		scsiDev.cdb[8];
+
+	if (mode == 0)
+	{
+		uint32_t maxSize = sizeof(scsiDev.data) - 4;
+		// 4 byte header
+		scsiDev.data[0] = 0;
+		scsiDev.data[1] = (maxSize >> 16) & 0xff;
+		scsiDev.data[2] = (maxSize >> 8) & 0xff;
+		scsiDev.data[3] = maxSize & 0xff;
+
+		scsiDev.dataLen =
+			(allocLength > sizeof(scsiDev.data)) ? sizeof(scsiDev.data) : allocLength;
+		scsiDev.phase = DATA_IN;
+	}
+	else if (mode == 0x2 && (scsiDev.cdb[2] == 0))
+	{
+		// TODO support BUFFER OFFSET fields in CDB
+		scsiDev.dataLen =
+			(allocLength > sizeof(scsiDev.data)) ? sizeof(scsiDev.data) : allocLength;
+		scsiDev.phase = DATA_IN;
+	}
+	else if (mode == 0x3)
+	{
+		uint32_t maxSize = sizeof(scsiDev.data) - 4;
+		// 4 byte header
+		scsiDev.data[0] = 0;
+		scsiDev.data[1] = (maxSize >> 16) & 0xff;
+		scsiDev.data[2] = (maxSize >> 8) & 0xff;
+		scsiDev.data[3] = maxSize & 0xff;
+
+		scsiDev.dataLen =
+			(allocLength > 4) ? 4: allocLength;
+		scsiDev.phase = DATA_IN;
+	}
+	else
+	{
+		// error.
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+}
+
+// Callback after the DATA OUT phase is complete.
+static void doWriteBuffer(void)
+{
+	if (scsiDev.status == GOOD) // skip if we've already encountered an error
+	{
+		// scsiDev.dataLen bytes are in scsiDev.data
+		// Don't shift it down 4 bytes ... this space is taken by
+		// the read buffer header anyway
+		scsiDev.phase = STATUS;
+	}
+}
+
+void scsiWriteBuffer()
+{
+	// WRITE BUFFER
+	// Used for testing the speed of the SCSI interface.
+	uint8_t mode = scsiDev.data[1] & 7;
+
+	int allocLength =
+		(((uint32_t) scsiDev.cdb[6]) << 16) +
+		(((uint32_t) scsiDev.cdb[7]) << 8) +
+		scsiDev.cdb[8];
+
+	if ((mode == 0 || mode == 2) && allocLength <= sizeof(scsiDev.data))
+	{
+		scsiDev.dataLen = allocLength;
+		scsiDev.phase = DATA_OUT;
+		scsiDev.postDataOutHook = doWriteBuffer;
+	}
+	else
+	{
+		// error.
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+}
+
+// XEBEC specific command. See
+// http://www.bitsavers.org/pdf/westernDigital/WD100x/79-000004_WD1002-SHD_OEM_Manual_Aug1984.pdf
+// Section 4.3.14
+void scsiWriteSectorBuffer()
+{
+	scsiDev.dataLen = scsiDev.target->liveCfg.bytesPerSector;
+	scsiDev.phase = DATA_OUT;
+	scsiDev.postDataOutHook = doWriteBuffer;
+}
+
+

+ 26 - 0
lib/SCSI2SD/src/firmware/diagnostic.h

@@ -0,0 +1,26 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef DIAGNOSTIC_H
+#define DIAGNOSTIC_H
+
+void scsiSendDiagnostic(void);
+void scsiReceiveDiagnostic(void);
+void scsiWriteBuffer(void);
+void scsiWriteSectorBuffer(void);
+void scsiReadBuffer(void);
+
+#endif

+ 58 - 0
lib/SCSI2SD/src/firmware/disk.h

@@ -0,0 +1,58 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef DISK_H
+#define DISK_H
+
+typedef enum
+{
+	// DISK_STARTED is stored per-target now as it's controlled by the
+	// START STOP UNIT command
+	OBSOLETE_DISK_STARTED = 1,
+	DISK_PRESENT = 2,     // SD card is physically present
+	DISK_INITIALISED = 4, // SD card responded to init sequence
+	DISK_WP = 8           // Write-protect.
+} DISK_STATE;
+
+typedef enum
+{
+	TRANSFER_READ,
+	TRANSFER_WRITE
+} TRANSFER_DIR;
+
+typedef struct
+{
+	int state;
+} BlockDevice;
+
+typedef struct
+{
+	int multiBlock; // True if we're using a multi-block SPI transfer.
+	uint32_t lba;
+	uint32_t blocks;
+
+	uint32_t currentBlock;
+} Transfer;
+
+extern BlockDevice blockDev;
+extern Transfer transfer;
+
+void scsiDiskInit(void);
+void scsiDiskReset(void);
+void scsiDiskPoll(void);
+int scsiDiskCommand(void);
+
+#endif

+ 228 - 0
lib/SCSI2SD/src/firmware/geometry.c

@@ -0,0 +1,228 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "geometry.h"
+#include "scsi.h"
+#include "sd.h"
+#include "config.h"
+
+#include <string.h>
+
+uint32_t getScsiCapacity(
+	uint32_t sdSectorStart,
+	uint16_t bytesPerSector,
+	uint32_t scsiSectors)
+{
+	uint32_t capacity =
+		(sdDev.capacity - sdSectorStart - S2S_CFG_SIZE) /
+			SDSectorsPerSCSISector(bytesPerSector);
+
+
+	if (sdDev.capacity == 0)
+	{
+		capacity = 0;
+	}
+	else if (sdSectorStart >= (sdDev.capacity - S2S_CFG_SIZE))
+	{
+		capacity = 0;
+	}
+	else if (scsiSectors && (capacity > scsiSectors))
+	{
+		capacity = scsiSectors;
+	}
+	return capacity;
+}
+
+
+uint32_t SCSISector2SD(
+	uint32_t sdSectorStart,
+	uint16_t bytesPerSector,
+	uint32_t scsiSector)
+{
+	return scsiSector * SDSectorsPerSCSISector(bytesPerSector) + sdSectorStart;
+}
+
+// Standard mapping according to ECMA-107 and ISO/IEC 9293:1994
+// Sector always starts at 1. There is no 0 sector.
+uint64_t CHS2LBA(
+	uint32_t c,
+	uint8_t h,
+	uint32_t s,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack)
+{
+	return (
+		(((uint64_t)c) * headsPerCylinder + h) *
+			(uint64_t) sectorsPerTrack
+		) + (s - 1);
+}
+
+
+void LBA2CHS(
+	uint32_t lba,
+	uint32_t* c,
+	uint8_t* h,
+	uint32_t* s,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack)
+{
+	*c = lba / (((uint32_t) sectorsPerTrack) * headsPerCylinder);
+	*h = (lba / sectorsPerTrack) % headsPerCylinder;
+	*s = (lba % sectorsPerTrack) + 1;
+}
+
+uint64_t scsiByteAddress(
+	uint16_t bytesPerSector,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack,
+	int format,
+	const uint8_t* addr)
+{
+	uint64_t result;
+	switch (format)
+	{
+	case ADDRESS_BLOCK:
+	{
+		uint32_t lba =
+			(((uint32_t) addr[0]) << 24) +
+			(((uint32_t) addr[1]) << 16) +
+			(((uint32_t) addr[2]) << 8) +
+			addr[3];
+
+		result = (uint64_t) bytesPerSector * lba;
+	} break;
+
+	case ADDRESS_PHYSICAL_BYTE:
+	{
+		uint32_t cyl =
+			(((uint32_t) addr[0]) << 16) +
+			(((uint32_t) addr[1]) << 8) +
+			addr[2];
+
+		uint8_t head = addr[3];
+
+		uint32_t bytes =
+			(((uint32_t) addr[4]) << 24) +
+			(((uint32_t) addr[5]) << 16) +
+			(((uint32_t) addr[6]) << 8) +
+			addr[7];
+
+		result = CHS2LBA(cyl, head, 1, headsPerCylinder, sectorsPerTrack) *
+			(uint64_t) bytesPerSector + bytes;
+	} break;
+
+	case ADDRESS_PHYSICAL_SECTOR:
+	{
+		uint32_t cyl =
+			(((uint32_t) addr[0]) << 16) +
+			(((uint32_t) addr[1]) << 8) +
+			addr[2];
+
+		uint8_t head = scsiDev.data[3];
+
+		uint32_t sector =
+			(((uint32_t) addr[4]) << 24) +
+			(((uint32_t) addr[5]) << 16) +
+			(((uint32_t) addr[6]) << 8) +
+			addr[7];
+
+		result = CHS2LBA(cyl, head, sector, headsPerCylinder, sectorsPerTrack) * (uint64_t) bytesPerSector;
+	} break;
+
+	default:
+		result = (uint64_t) -1;
+	}
+
+	return result;
+}
+
+
+void scsiSaveByteAddress(
+	uint16_t bytesPerSector,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack,
+	int format,
+	uint64_t byteAddr,
+	uint8_t* buf)
+{
+	uint32_t lba = byteAddr / bytesPerSector;
+	uint32_t byteOffset = byteAddr % bytesPerSector;
+
+	switch (format)
+	{
+	case ADDRESS_BLOCK:
+	{
+		buf[0] = lba >> 24;
+		buf[1] = lba >> 16;
+		buf[2] = lba >> 8;
+		buf[3] = lba;
+
+		buf[4] = 0;
+		buf[5] = 0;
+		buf[6] = 0;
+		buf[7] = 0;
+	} break;
+
+	case ADDRESS_PHYSICAL_BYTE:
+	{
+		uint32_t cyl;
+		uint8_t head;
+		uint32_t sector;
+		uint32_t bytes;
+
+		LBA2CHS(lba, &cyl, &head, &sector, headsPerCylinder, sectorsPerTrack);
+
+		bytes = sector * bytesPerSector + byteOffset;
+
+		buf[0] = cyl >> 16;
+		buf[1] = cyl >> 8;
+		buf[2] = cyl;
+
+		buf[3] = head;
+
+		buf[4] = bytes >> 24;
+		buf[5] = bytes >> 16;
+		buf[6] = bytes >> 8;
+		buf[7] = bytes;
+	} break;
+
+	case ADDRESS_PHYSICAL_SECTOR:
+	{
+		uint32_t cyl;
+		uint8_t head;
+		uint32_t sector;
+
+		LBA2CHS(lba, &cyl, &head, &sector, headsPerCylinder, sectorsPerTrack);
+
+		buf[0] = cyl >> 16;
+		buf[1] = cyl >> 8;
+		buf[2] = cyl;
+
+		buf[3] = head;
+
+		buf[4] = sector >> 24;
+		buf[5] = sector >> 16;
+		buf[6] = sector >> 8;
+		buf[7] = sector;
+	} break;
+
+	default:
+		memset(buf, 0, 8);
+	}
+
+}
+

+ 78 - 0
lib/SCSI2SD/src/firmware/geometry.h

@@ -0,0 +1,78 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef GEOMETRY_H
+#define GEOMETRY_H
+
+#include "config.h"
+// TODO #include "sd.h"
+#define SD_SECTOR_SIZE 512
+
+typedef enum
+{
+	ADDRESS_BLOCK = 0,
+	ADDRESS_PHYSICAL_BYTE = 4,
+	ADDRESS_PHYSICAL_SECTOR = 5
+} SCSI_ADDRESS_FORMAT;
+
+static inline int SDSectorsPerSCSISector(uint16_t bytesPerSector)
+{
+	return (bytesPerSector + SD_SECTOR_SIZE - 1) / SD_SECTOR_SIZE;
+}
+
+uint32_t getScsiCapacity(
+	uint32_t sdSectorStart,
+	uint16_t bytesPerSector,
+	uint32_t scsiSectors);
+
+uint32_t SCSISector2SD(
+	uint32_t sdSectorStart,
+	uint16_t bytesPerSector,
+	uint32_t scsiSector);
+
+uint64_t CHS2LBA(
+	uint32_t c,
+	uint8_t h,
+	uint32_t s,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack);
+void LBA2CHS(
+	uint32_t lba,
+	uint32_t* c,
+	uint8_t* h,
+	uint32_t* s,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack);
+
+// Convert an address in the given SCSI_ADDRESS_FORMAT to
+// a linear byte address.
+// addr must be >= 8 bytes.
+uint64_t scsiByteAddress(
+	uint16_t bytesPerSector,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack,
+	int format,
+	const uint8_t* addr);
+void scsiSaveByteAddress(
+	uint16_t bytesPerSector,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack,
+	int format,
+	uint64_t byteAddr,
+	uint8_t* buf);
+
+
+#endif

+ 267 - 0
lib/SCSI2SD/src/firmware/inquiry.c

@@ -0,0 +1,267 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//	Copyright (C) 2019 Landon Rodgers  <g.landon.rodgers@gmail.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "config.h"
+#include "inquiry.h"
+
+#include <string.h>
+
+static uint8_t StandardResponse[] =
+{
+0x00, // "Direct-access device". AKA standard hard disk
+0x00, // device type modifier
+0x02, // Complies with ANSI SCSI-2.
+0x01, // Response format is compatible with the old CCS format.
+0x1f, // standard length.
+0, 0, // Reserved
+0x18 // Enable sync and linked commands
+};
+// Vendor set by config 'c','o','d','e','s','r','c',' ',
+// prodId set by config'S','C','S','I','2','S','D',' ',' ',' ',' ',' ',' ',' ',' ',' ',
+// Revision set by config'2','.','0','a'
+
+
+static const uint8_t SupportedVitalPages[] =
+{
+0x00, // "Direct-access device". AKA standard hard disk
+0x00, // Page Code
+0x00, // Reserved
+0x04, // Page length
+0x00, // Support "Supported vital product data pages"
+0x80, // Support "Unit serial number page"
+0x81, // Support "Implemented operating definition page"
+0x82 // Support "ASCII Implemented operating definition page"
+};
+
+static const uint8_t UnitSerialNumber[] =
+{
+0x00, // "Direct-access device". AKA standard hard disk
+0x80, // Page Code
+0x00, // Reserved
+0x10, // Page length
+'c','o','d','e','s','r','c','-','1','2','3','4','5','6','7','8'
+};
+
+static const uint8_t ImpOperatingDefinition[] =
+{
+0x00, // "Direct-access device". AKA standard hard disk
+0x81, // Page Code
+0x00, // Reserved
+0x03, // Page length
+0x03, // Current: SCSI-2 operating definition
+0x03, // Default: SCSI-2 operating definition
+0x03 // Supported (list): SCSI-2 operating definition.
+};
+
+static const uint8_t AscImpOperatingDefinition[] =
+{
+0x00, // "Direct-access device". AKA standard hard disk
+0x82, // Page Code
+0x00, // Reserved
+0x07, // Page length
+0x06, // Ascii length
+'S','C','S','I','-','2'
+};
+
+void s2s_scsiInquiry()
+{
+	uint8_t evpd = scsiDev.cdb[1] & 1; // enable vital product data.
+	uint8_t pageCode = scsiDev.cdb[2];
+	uint32_t allocationLength = scsiDev.cdb[4];
+
+	// SASI standard, X3T9.3_185_RevE  states that 0 == 256 bytes
+	// BUT SCSI 2 standard says 0 == 0.
+	if (scsiDev.compatMode <= COMPAT_SCSI1) // excludes COMPAT_SCSI2_DISABLED
+	{
+		if (allocationLength == 0) allocationLength = 256;
+	}
+
+	if (!evpd)
+	{
+		if (pageCode)
+		{
+			// error.
+			scsiDev.status = CHECK_CONDITION;
+			scsiDev.target->sense.code = ILLEGAL_REQUEST;
+			scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+			scsiDev.phase = STATUS;
+		}
+		else
+		{
+			const S2S_TargetCfg* config = scsiDev.target->cfg;
+			scsiDev.dataLen =
+				s2s_getStandardInquiry(
+					config,
+					scsiDev.data,
+					sizeof(scsiDev.data));
+			scsiDev.phase = DATA_IN;
+		}
+	}
+	else if (pageCode == 0x00)
+	{
+		memcpy(scsiDev.data, SupportedVitalPages, sizeof(SupportedVitalPages));
+		scsiDev.dataLen = sizeof(SupportedVitalPages);
+		scsiDev.phase = DATA_IN;
+	}
+	else if (pageCode == 0x80)
+	{
+		memcpy(scsiDev.data, UnitSerialNumber, sizeof(UnitSerialNumber));
+		scsiDev.dataLen = sizeof(UnitSerialNumber);
+        const S2S_TargetCfg* config = scsiDev.target->cfg;
+        memcpy(&scsiDev.data[4], config->serial, sizeof(config->serial));
+		scsiDev.phase = DATA_IN;
+	}
+	else if (pageCode == 0x81)
+	{
+		memcpy(
+			scsiDev.data,
+			ImpOperatingDefinition,
+			sizeof(ImpOperatingDefinition));
+		scsiDev.dataLen = sizeof(ImpOperatingDefinition);
+		scsiDev.phase = DATA_IN;
+	}
+	else if (pageCode == 0x82)
+	{
+		memcpy(
+			scsiDev.data,
+			AscImpOperatingDefinition,
+			sizeof(AscImpOperatingDefinition));
+		scsiDev.dataLen = sizeof(AscImpOperatingDefinition);
+		scsiDev.phase = DATA_IN;
+	}
+	else
+	{
+		// error.
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+
+
+	if (scsiDev.phase == DATA_IN)
+	{
+		// VAX workaround
+		if (allocationLength == 255 &&
+			(scsiDev.target->cfg->quirks & S2S_CFG_QUIRKS_VMS))
+		{
+			allocationLength = 254;
+		}
+
+		// "real" hard drives send back exactly allocationLenth bytes, padded
+		// with zeroes. This only seems to happen for Inquiry responses, and not
+		// other commands that also supply an allocation length such as Mode Sense or
+		// Request Sense.
+		// (See below for exception to this rule when 0 allocation length)
+		if (scsiDev.dataLen < allocationLength)
+		{
+			memset(
+				&scsiDev.data[scsiDev.dataLen],
+				0,
+				allocationLength - scsiDev.dataLen);
+		}
+		// Spec 8.2.5 requires us to simply truncate the response if it's
+		// too big.
+		scsiDev.dataLen = allocationLength;
+
+		// Set the device type as needed.
+		scsiDev.data[0] = getDeviceTypeQualifier();
+
+		switch (scsiDev.target->cfg->deviceType)
+		{
+		case S2S_CFG_OPTICAL:
+			scsiDev.data[1] |= 0x80; // Removable bit.
+			break;
+
+		case S2S_CFG_SEQUENTIAL:
+			scsiDev.data[1] |= 0x80; // Removable bit.
+			break;
+
+		case S2S_CFG_MO:
+			scsiDev.data[1] |= 0x80; // Removable bit.
+			break;
+
+		case S2S_CFG_FLOPPY_14MB:
+		case S2S_CFG_REMOVEABLE:
+			scsiDev.data[1] |= 0x80; // Removable bit.
+			break;
+		default:
+			// Accept defaults for a fixed disk.
+			break;
+		}
+	}
+
+	// Set the first byte to indicate LUN presence.
+	if (scsiDev.lun) // We only support lun 0
+	{
+		scsiDev.data[0] = 0x7F;
+	}
+}
+
+uint32_t s2s_getStandardInquiry(
+	const S2S_TargetCfg* cfg, uint8_t* out, uint32_t maxlen
+	)
+{
+	uint32_t buflen = sizeof(StandardResponse);
+	if (buflen > maxlen) buflen = maxlen;
+
+	memcpy(out, StandardResponse, buflen);
+	out[1] = cfg->deviceTypeModifier;
+
+	if (scsiDev.compatMode >= COMPAT_SCSI2)
+	{
+		out[3] = 2; // SCSI 2 response format.
+	}
+	memcpy(&out[8], cfg->vendor, sizeof(cfg->vendor));
+	memcpy(&out[16], cfg->prodId, sizeof(cfg->prodId));
+	memcpy(&out[32], cfg->revision, sizeof(cfg->revision));
+	return sizeof(StandardResponse) +
+		sizeof(cfg->vendor) +
+		sizeof(cfg->prodId) +
+		sizeof(cfg->revision);
+}
+
+uint8_t getDeviceTypeQualifier()
+{
+	// Set the device type as needed.
+	switch (scsiDev.target->cfg->deviceType)
+	{
+	case S2S_CFG_OPTICAL:
+		return 0x05;
+		break;
+
+	case S2S_CFG_SEQUENTIAL:
+		return 0x01;
+		break;
+
+	case S2S_CFG_MO:
+		return 0x07;
+		break;
+
+	case S2S_CFG_FLOPPY_14MB:
+	case S2S_CFG_REMOVEABLE:
+		return 0;
+		break;
+
+	default:
+		// Accept defaults for a fixed disk.
+		return 0;
+	}
+}
+

+ 25 - 0
lib/SCSI2SD/src/firmware/inquiry.h

@@ -0,0 +1,25 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef S2S_INQUIRY_H
+#define S2S_INQUIRY_H
+
+void s2s_scsiInquiry(void);
+uint32_t s2s_getStandardInquiry(const S2S_TargetCfg* cfg, uint8_t* out, uint32_t maxlen);
+uint8_t getDeviceTypeQualifier(void);
+
+
+#endif

+ 24 - 0
lib/SCSI2SD/src/firmware/led.h

@@ -0,0 +1,24 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef S2S_LED_H
+#define S2S_LED_H
+
+void s2s_ledInit(void);
+void s2s_ledOn(void);
+void s2s_ledOff(void);
+
+#endif

+ 39 - 0
lib/SCSI2SD/src/firmware/mo.c

@@ -0,0 +1,39 @@
+//	Copyright (C) 2015 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "config.h"
+#include "mo.h"
+
+
+// Handle magneto-optical scsi device commands
+int scsiMOCommand()
+{
+	int commandHandled = 0;
+
+	uint8_t command = scsiDev.cdb[0];
+	if ((command == 0x2C) || // ERASE(10)
+		(command == 0xAC)) // ERASE(12)
+	{
+		// TODO consider sending an erase command to the SD card.
+
+		commandHandled = 1;
+	}
+
+	return commandHandled;
+}
+

+ 22 - 0
lib/SCSI2SD/src/firmware/mo.h

@@ -0,0 +1,22 @@
+//	Copyright (C) 2015 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef MO_H
+#define MO_H
+
+int scsiMOCommand(void);
+
+#endif

+ 746 - 0
lib/SCSI2SD/src/firmware/mode.c

@@ -0,0 +1,746 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//  Copyright (C) 2014 Doug Brown <doug@downtowndougbrown.com>
+//  Copyright (C) 2019 Landon Rodgers <g.landon.rodgers@gmail.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "mode.h"
+#include "disk.h"
+#include "inquiry.h"
+
+#include <string.h>
+
+// "Vendor" defined page which was included by Seagate, and required for\r
+// Amiga 500 using DKB SpitFire controller.\r
+static const uint8_t OperatingPage[] =
+{
+0x00, // Page code
+0x02, // Page length
+
+// Bit 4 = unit attension (0 = on, 1 = off).
+// Bit 7 = usage bit, EEPROM life exceeded warning = 1.
+0x80, 
+
+// Bit 7 = reserved.
+// Bits 0:6: Device type qualifier, as per Inquiry data
+0x00
+};
+
+static const uint8_t ReadWriteErrorRecoveryPage[] =
+{
+0x01, // Page code
+0x0A, // Page length
+
+// VMS 5.5-2 is very particular regarding the mode page values.
+// The required values for a SCSI2/NoTCQ device are:
+// AWRE=0 ARRE=0 TB=1 RC=0 EER=? PER=1 DTE=1 DCR=?
+// See ftp://www.digiater.nl/openvms/decus/vms94b/net94b/scsi_params_dkdriver.txt
+// X-Newsgroups: comp.os.vms
+// Subject: Re: VMS 6.1 vs. Seagate Disk Drives
+// Message-Id: <32g87h$8q@nntpd.lkg.dec.com>
+// From: weber@evms.enet.dec.com (Ralph O. Weber -- OpenVMS AXP)
+// Date: 12 Aug 1994 16:32:49 GMT
+0x26,
+
+0x00, // Don't try recovery algorithm during reads
+0x00, // Correction span 0
+0x00, // Head offset count 0,
+0x00, // Data strobe offset count 0,
+0x00, // Reserved
+0x00, // Don't try recovery algorithm during writes
+0x00, // Reserved
+0x00, 0x00 // Recovery time limit 0 (use default)*/
+};
+
+static const uint8_t ReadWriteErrorRecoveryPage_SCSI1[] =
+{
+0x01, // Page code
+0x06, // Page length
+0x26,
+0x00, // Don't try recovery algorithm during reads
+0x00, // Correction span 0
+0x00, // Head offset count 0,
+0x00, // Data strobe offset count 0,
+0xFF // Reserved
+};
+
+static const uint8_t DisconnectReconnectPage[] =
+{
+0x02, // Page code
+0x0E, // Page length
+0, // Buffer full ratio
+0, // Buffer empty ratio
+0x00, 10, // Bus inactivity limit, 100us increments. Allow 1ms.
+0x00, 0x00, // Disconnect time limit
+0x00, 0x00, // Connect time limit
+0x00, 0x00, // Maximum burst size
+0x00 ,// DTDC. Not used.
+0x00, 0x00, 0x00 // Reserved
+};
+
+static const uint8_t DisconnectReconnectPage_SCSI1[] =
+{
+0x02, // Page code
+0x0A, // Page length
+0, // Buffer full ratio
+0, // Buffer empty ratio
+0x00, 10, // Bus inactivity limit, 100us increments. Allow 1ms.
+0x00, 0x00, // Disconnect time limit
+0x00, 0x00, // Connect time limit
+0x00, 0x00 // Maximum burst size
+};
+
+static const uint8_t FormatDevicePage[] =
+{
+0x03 | 0x80, // Page code | PS (persist) bit.
+0x16, // Page length
+0x00, 0x00, // Single zone
+0x00, 0x00, // No alternate sectors
+0x00, 0x00, // No alternate tracks
+0x00, 0x00, // No alternate tracks per lun
+0x00, 0x00, // Sectors per track, configurable
+0xFF, 0xFF, // Data bytes per physical sector. Configurable.
+0x00, 0x01, // Interleave
+0x00, 0x00, // Track skew factor
+0x00, 0x00, // Cylinder skew factor
+0xC0, // SSEC(set) HSEC(set) RMB SURF
+0x00, 0x00, 0x00 // Reserved
+};
+
+static const uint8_t RigidDiskDriveGeometry[] =
+{
+0x04, // Page code
+0x16, // Page length
+0xFF, 0xFF, 0xFF, // Number of cylinders
+0x00, // Number of heads (replaced by configured value)
+0xFF, 0xFF, 0xFF, // Starting cylinder-write precompensation
+0xFF, 0xFF, 0xFF, // Starting cylinder-reduced write current
+0x00, 0x1, // Drive step rate (units of 100ns)
+0x00, 0x00, 0x00, // Landing zone cylinder
+0x00, // RPL
+0x00, // Rotational offset
+0x00, // Reserved
+5400 >> 8, 5400 & 0xFF, // Medium rotation rate (RPM)
+0x00, 0x00 // Reserved
+};
+
+static const uint8_t FlexibleDiskDriveGeometry[] =
+{
+0x05, // Page code
+0x1E, // Page length
+0x01, 0xF4, // Transfer Rate (500kbits)
+0x01, // heads
+18, // sectors per track
+0x20,0x00, // bytes per sector
+0x00, 80, // Cylinders
+0x00, 0x80, // Write-precomp
+0x00, 0x80, // reduced current,
+0x00, 0x00, // Drive step rate
+0x00, // pulse width
+0x00, 0x00, // Head settle delay
+0x00, // motor on delay
+0x00,  // motor off delay
+0x00,
+0x00,
+0x00,
+0x00,
+0x00,
+0x00,
+0x00,
+0x00,
+0x00,
+0x00,
+0x00
+};
+
+static const uint8_t RigidDiskDriveGeometry_SCSI1[] =
+{
+0x04, // Page code
+0x12, // Page length
+0xFF, 0xFF, 0xFF, // Number of cylinders
+0x00, // Number of heads (replaced by configured value)
+0xFF, 0xFF, 0xFF, // Starting cylinder-write precompensation
+0xFF, 0xFF, 0xFF, // Starting cylinder-reduced write current
+0x00, 0x1, // Drive step rate (units of 100ns)
+0x00, 0x00, 0x00, // Landing zone cylinder
+0x00, // RPL
+0x00, // Rotational offset
+0x00 // Reserved
+};
+
+static const uint8_t CachingPage[] =
+{
+0x08, // Page Code
+0x0A, // Page length
+0x01, // Read cache disable
+0x00, // No useful rention policy.
+0x00, 0x00, // Pre-fetch always disabled
+0x00, 0x00, // Minimum pre-fetch
+0x00, 0x00, // Maximum pre-fetch
+0x00, 0x00, // Maximum pre-fetch ceiling
+};
+
+// Old CCS SCSI-1 cache page
+static const uint8_t CCSCachingPage[] =
+{
+0x38, // Page Code
+0x0E, // Page length
+0x00, // Read cache disable
+0x00, // Prefetch threshold
+0x00, 0x00, // Max threshold / multiplier
+0x00, 0x00, // Min threshold / multiplier
+0x00, 0x00, // Reserved
+0x00, 0x00,
+0x00, 0x00,
+0x00, 0x00,
+};
+
+static const uint8_t ControlModePage[] =
+{
+0x0A, // Page code
+0x06, // Page length
+0x00, // No logging
+0x01, // Disable tagged queuing
+0x00, // No async event notifications
+0x00, // Reserved
+0x00, 0x00 // AEN holdoff period.
+};
+
+static const uint8_t SequentialDeviceConfigPage[] =
+{
+0x10, // page code
+0x0E, // Page length
+0x00, // CAP, CAF, Active Format
+0x00, // Active partition
+0x00, // Write buffer full ratio
+0x00, // Read buffer empty ratio
+0x00,0x01, // Write delay time, in 100ms units
+0x00, // Default gap size
+0x10, // auto-generation of default eod (end of data)
+0x00,0x00,0x00, // buffer-size at early warning
+0x00, // No data compression
+0x00 // reserved
+};
+
+// Allow Apple 68k Drive Setup to format this drive.
+// Code
+static const uint8_t AppleVendorPage[] =
+{
+0x30, // Page code
+23, // Page length
+'A','P','P','L','E',' ','C','O','M','P','U','T','E','R',',',' ','I','N','C',' ',' ',' ',0x00
+};
+
+static void pageIn(int pc, int dataIdx, const uint8_t* pageData, int pageLen)
+{
+	memcpy(&scsiDev.data[dataIdx], pageData, pageLen);
+
+	if (pc == 0x01) // Mask out (un)changable values
+	{
+		memset(&scsiDev.data[dataIdx+2], 0, pageLen - 2);
+	}
+}
+
+static void doModeSense(
+	int sixByteCmd, int dbd, int pc, int pageCode, int allocLength)
+{
+	////////////// Mode Parameter Header
+	////////////////////////////////////
+
+	// Skip the Mode Data Length, we set that last.
+	int idx = 1;
+	if (!sixByteCmd) ++idx;
+
+	uint8_t mediumType = 0;
+	uint8_t deviceSpecificParam = 0;
+	uint8_t density = 0;
+	switch (scsiDev.target->cfg->deviceType)
+	{
+	case S2S_CFG_FIXED:
+	case S2S_CFG_REMOVEABLE:
+		mediumType = 0; // We should support various floppy types here!
+		// Contains cache bits (0) and a Write-Protect bit.
+		deviceSpecificParam =
+			(blockDev.state & DISK_WP) ? 0x80 : 0;
+		density = 0; // reserved for direct access
+		break;
+
+	case S2S_CFG_FLOPPY_14MB:
+		mediumType = 0x1E; // 90mm/3.5"
+		deviceSpecificParam =
+			(blockDev.state & DISK_WP) ? 0x80 : 0;
+		density = 0; // reserved for direct access
+		break;
+
+	case S2S_CFG_OPTICAL:
+		mediumType = 0x02; // 120mm CDROM, data only.
+		deviceSpecificParam = 0;
+		density = 0x01; // User data only, 2048bytes per sector.
+		break;
+
+	case S2S_CFG_SEQUENTIAL:
+		mediumType = 0; // reserved
+		deviceSpecificParam =
+			(blockDev.state & DISK_WP) ? 0x80 : 0;
+		density = 0x13; // DAT Data Storage, X3B5/88-185A 
+		break;
+
+	case S2S_CFG_MO:
+        mediumType = 0x03; // Optical reversible or erasable medium
+		deviceSpecificParam =
+			(blockDev.state & DISK_WP) ? 0x80 : 0;
+		density = 0x00; // Default
+		break;
+
+	};
+
+	scsiDev.data[idx++] = mediumType;
+	scsiDev.data[idx++] = deviceSpecificParam;
+
+	if (sixByteCmd)
+	{
+		if (dbd)
+		{
+			scsiDev.data[idx++] = 0; // No block descriptor
+		}
+		else
+		{
+			// One block descriptor of length 8 bytes.
+			scsiDev.data[idx++] = 8;
+		}
+	}
+	else
+	{
+		scsiDev.data[idx++] = 0; // Reserved
+		scsiDev.data[idx++] = 0; // Reserved
+		if (dbd)
+		{
+			scsiDev.data[idx++] = 0; // No block descriptor
+			scsiDev.data[idx++] = 0; // No block descriptor
+		}
+		else
+		{
+			// One block descriptor of length 8 bytes.
+			scsiDev.data[idx++] = 0;
+			scsiDev.data[idx++] = 8;
+		}
+	}
+
+	////////////// Block Descriptor
+	////////////////////////////////////
+	if (!dbd)
+	{
+		scsiDev.data[idx++] = density;
+		// Number of blocks
+		// Zero == all remaining blocks shall have the medium
+		// characteristics specified.
+		scsiDev.data[idx++] = 0;
+		scsiDev.data[idx++] = 0;
+		scsiDev.data[idx++] = 0;
+
+		scsiDev.data[idx++] = 0; // reserved
+
+		// Block length
+		uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
+		scsiDev.data[idx++] = bytesPerSector >> 16;
+		scsiDev.data[idx++] = bytesPerSector >> 8;
+		scsiDev.data[idx++] = bytesPerSector & 0xFF;
+	}
+
+	int pageFound = 0;
+
+	if (pageCode == 0x01 || pageCode == 0x3F)
+	{
+		pageFound = 1;
+		if ((scsiDev.compatMode >= COMPAT_SCSI2))
+		{
+			pageIn(pc, idx, ReadWriteErrorRecoveryPage, sizeof(ReadWriteErrorRecoveryPage));
+			idx += sizeof(ReadWriteErrorRecoveryPage);
+		}
+		else
+		{
+			pageIn(pc, idx, ReadWriteErrorRecoveryPage_SCSI1, sizeof(ReadWriteErrorRecoveryPage_SCSI1));
+			idx += sizeof(ReadWriteErrorRecoveryPage_SCSI1);
+		}
+	}
+
+	if (pageCode == 0x02 || pageCode == 0x3F)
+	{
+		pageFound = 1;
+		if ((scsiDev.compatMode >= COMPAT_SCSI2))
+		{
+			pageIn(pc, idx, DisconnectReconnectPage, sizeof(DisconnectReconnectPage));
+			idx += sizeof(DisconnectReconnectPage);
+		}
+		else
+		{
+			pageIn(pc, idx, DisconnectReconnectPage_SCSI1, sizeof(DisconnectReconnectPage_SCSI1));
+			idx += sizeof(DisconnectReconnectPage_SCSI1);
+		}
+	}
+
+	if (pageCode == 0x03 || pageCode == 0x3F)
+	{
+		pageFound = 1;
+		pageIn(pc, idx, FormatDevicePage, sizeof(FormatDevicePage));
+		if (pc != 0x01)
+		{
+			uint16_t sectorsPerTrack = scsiDev.target->cfg->sectorsPerTrack;
+			scsiDev.data[idx+10] = sectorsPerTrack >> 8;
+			scsiDev.data[idx+11] = sectorsPerTrack & 0xFF;
+
+			// Fill out the configured bytes-per-sector
+			uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
+			scsiDev.data[idx+12] = bytesPerSector >> 8;
+			scsiDev.data[idx+13] = bytesPerSector & 0xFF;
+		}
+		else
+		{
+			// Set a mask for the changeable values.
+			scsiDev.data[idx+12] = 0xFF;
+			scsiDev.data[idx+13] = 0xFF;
+		}
+
+		idx += sizeof(FormatDevicePage);
+	}
+
+	if (pageCode == 0x04 || pageCode == 0x3F)
+	{
+		pageFound = 1;
+		if ((scsiDev.compatMode >= COMPAT_SCSI2))
+		{
+			pageIn(pc, idx, RigidDiskDriveGeometry, sizeof(RigidDiskDriveGeometry));
+		}
+		else
+		{
+			pageIn(pc, idx, RigidDiskDriveGeometry_SCSI1, sizeof(RigidDiskDriveGeometry_SCSI1));
+		}
+
+		if (pc != 0x01)
+		{
+			// Need to fill out the number of cylinders.
+			uint32_t cyl;
+			uint8_t head;
+			uint32_t sector;
+			LBA2CHS(
+				getScsiCapacity(
+					scsiDev.target->cfg->sdSectorStart,
+					scsiDev.target->liveCfg.bytesPerSector,
+					scsiDev.target->cfg->scsiSectors),
+				&cyl,
+				&head,
+				&sector,
+				scsiDev.target->cfg->headsPerCylinder,
+				scsiDev.target->cfg->sectorsPerTrack);
+
+			scsiDev.data[idx+2] = cyl >> 16;
+			scsiDev.data[idx+3] = cyl >> 8;
+			scsiDev.data[idx+4] = cyl;
+
+			memcpy(&scsiDev.data[idx+6], &scsiDev.data[idx+2], 3);
+			memcpy(&scsiDev.data[idx+9], &scsiDev.data[idx+2], 3);
+
+			scsiDev.data[idx+5] = scsiDev.target->cfg->headsPerCylinder;
+		}
+
+		if ((scsiDev.compatMode >= COMPAT_SCSI2))
+		{
+			idx += sizeof(RigidDiskDriveGeometry);
+		}
+		else
+		{
+			idx += sizeof(RigidDiskDriveGeometry_SCSI1);
+		}
+	}
+
+	if ((pageCode == 0x05 || pageCode == 0x3F) &&
+		(scsiDev.target->cfg->deviceType == S2S_CFG_FLOPPY_14MB))
+	{
+		pageFound = 1;
+		pageIn(pc, idx, FlexibleDiskDriveGeometry, sizeof(FlexibleDiskDriveGeometry));
+		idx += sizeof(FlexibleDiskDriveGeometry);
+	}
+
+	// DON'T output the following pages for SCSI1 hosts. They get upset when
+	// we have more data to send than the allocation length provided.
+	// (ie. Try not to output any more pages below this comment)
+
+
+	if ((scsiDev.compatMode >= COMPAT_SCSI2) &&
+		(pageCode == 0x08 || pageCode == 0x3F))
+	{
+		pageFound = 1;
+		pageIn(pc, idx, CachingPage, sizeof(CachingPage));
+		idx += sizeof(CachingPage);
+	}
+
+	if ((scsiDev.compatMode >= COMPAT_SCSI2)
+		&& (pageCode == 0x0A || pageCode == 0x3F))
+	{
+		pageFound = 1;
+		pageIn(pc, idx, ControlModePage, sizeof(ControlModePage));
+		idx += sizeof(ControlModePage);
+	}
+
+	if ((scsiDev.target->cfg->deviceType == S2S_CFG_SEQUENTIAL) &&
+		(pageCode == 0x10 || pageCode == 0x3F))
+	{
+		pageFound = 1;
+		pageIn(
+			pc,
+			idx,
+			SequentialDeviceConfigPage,
+			sizeof(SequentialDeviceConfigPage));
+		idx += sizeof(SequentialDeviceConfigPage);
+	}
+
+	if ((
+			(scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_APPLE) ||
+			(idx + sizeof(AppleVendorPage) <= allocLength)
+		) &&
+		(pageCode == 0x30 || pageCode == 0x3F))
+	{
+		pageFound = 1;
+		pageIn(pc, idx, AppleVendorPage, sizeof(AppleVendorPage));
+		idx += sizeof(AppleVendorPage);
+	}
+
+	if (pageCode == 0x38) // Don't send unless requested
+	{
+		pageFound = 1;
+		pageIn(pc, idx, CCSCachingPage, sizeof(CCSCachingPage));
+		idx += sizeof(CCSCachingPage);
+	}
+
+	// SCSI 2 standard says page 0 is always last.
+	if (pageCode == 0x00 || pageCode == 0x3F)
+	{
+		pageFound = 1;
+		pageIn(pc, idx, OperatingPage, sizeof(OperatingPage));
+
+		// Note inverted logic for the flag.
+		scsiDev.data[idx+2] =
+			(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_UNIT_ATTENTION) ? 0x80 : 0x90;
+
+		scsiDev.data[idx+3] = getDeviceTypeQualifier();
+
+		idx += sizeof(OperatingPage);
+	}
+
+	if (!pageFound)
+	{
+		// Unknown Page Code
+		pageFound = 0;
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+	else
+	{
+		// Go back and fill out the mode data length
+		if (sixByteCmd)
+		{
+			// Cannot currently exceed limits. yay
+			scsiDev.data[0] = idx - 1;
+		}
+		else
+		{
+			scsiDev.data[0] = ((idx - 2) >> 8);
+			scsiDev.data[1] = (idx - 2);
+		}
+
+		scsiDev.dataLen = idx > allocLength ? allocLength : idx;
+		scsiDev.phase = DATA_IN;
+	}
+}
+
+
+// Callback after the DATA OUT phase is complete.
+static void doModeSelect(void)
+{
+	if (scsiDev.status == GOOD) // skip if we've already encountered an error
+	{
+		// scsiDev.dataLen bytes are in scsiDev.data
+
+		int idx;
+		int blockDescLen;
+		if (scsiDev.cdb[0] == 0x55)
+		{
+			blockDescLen =
+				(((uint16_t)scsiDev.data[6]) << 8) |scsiDev.data[7];
+			idx = 8;
+		}
+		else
+		{
+			blockDescLen = scsiDev.data[3];
+			idx = 4;
+		}
+
+		// The unwritten rule.  Blocksizes are normally set using the
+		// block descriptor value, not by changing page 0x03.
+		if (blockDescLen >= 8)
+		{
+			uint32_t bytesPerSector =
+				(((uint32_t)scsiDev.data[idx+5]) << 16) |
+				(((uint32_t)scsiDev.data[idx+6]) << 8) |
+				scsiDev.data[idx+7];
+			if ((bytesPerSector < MIN_SECTOR_SIZE) ||
+				(bytesPerSector > MAX_SECTOR_SIZE))
+			{
+				goto bad;
+			}
+			else
+			{
+				scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;
+				if (bytesPerSector != scsiDev.target->cfg->bytesPerSector)
+				{
+					s2s_configSave(scsiDev.target->targetId, bytesPerSector);
+				}
+			}
+		}
+		idx += blockDescLen;
+
+		while (idx < scsiDev.dataLen)
+		{
+			int pageLen = scsiDev.data[idx + 1];
+			if (idx + 2 + pageLen > scsiDev.dataLen) goto bad;
+
+			int pageCode = scsiDev.data[idx] & 0x3F;
+			switch (pageCode)
+			{
+			case 0x03: // Format Device Page
+			{
+				if (pageLen != 0x16) goto bad;
+
+				// Fill out the configured bytes-per-sector
+				uint16_t bytesPerSector =
+					(((uint16_t)scsiDev.data[idx+12]) << 8) |
+					scsiDev.data[idx+13];
+
+				// Sane values only, ok ?
+				if ((bytesPerSector < MIN_SECTOR_SIZE) ||
+					(bytesPerSector > MAX_SECTOR_SIZE))
+				{
+					goto bad;
+				}
+
+				scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;
+				if (scsiDev.cdb[1] & 1) // SP Save Pages flag
+				{
+					s2s_configSave(scsiDev.target->targetId, bytesPerSector);
+				}
+			}
+			break;
+			//default:
+
+				// Easiest to just ignore for now. We'll get here when changing
+				// the SCSI block size via the descriptor header.
+			}
+			idx += 2 + pageLen;
+		}
+	}
+
+	goto out;
+bad:
+	scsiDev.status = CHECK_CONDITION;
+	scsiDev.target->sense.code = ILLEGAL_REQUEST;
+	scsiDev.target->sense.asc = INVALID_FIELD_IN_PARAMETER_LIST;
+
+out:
+	scsiDev.phase = STATUS;
+}
+
+int scsiModeCommand()
+{
+	int commandHandled = 1;
+
+	uint8_t command = scsiDev.cdb[0];
+
+	// We don't currently support the setting of any parameters.
+	// (ie. no MODE SELECT(6) or MODE SELECT(10) commands)
+
+	if (command == 0x1A)
+	{
+		// MODE SENSE(6)
+		int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors
+		int pc = scsiDev.cdb[2] >> 6; // Page Control
+		int pageCode = scsiDev.cdb[2] & 0x3F;
+		int allocLength = scsiDev.cdb[4];
+
+		// SCSI1 standard: (CCS X3T9.2/86-52)
+		// "An Allocation Length of zero indicates that no MODE SENSE data shall
+		// be transferred. This condition shall not be considered as an error."
+		doModeSense(1, dbd, pc, pageCode, allocLength);
+	}
+	else if (command == 0x5A)
+	{
+		// MODE SENSE(10)
+		int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors
+		int pc = scsiDev.cdb[2] >> 6; // Page Control
+		int pageCode = scsiDev.cdb[2] & 0x3F;
+		int allocLength =
+			(((uint16_t) scsiDev.cdb[7]) << 8) +
+			scsiDev.cdb[8];
+		doModeSense(0, dbd, pc, pageCode, allocLength);
+	}
+	else if (command == 0x15)
+	{
+		// MODE SELECT(6)
+		int len = scsiDev.cdb[4];
+		if (len == 0)
+		{
+			// If len == 0, then transfer no data. From the SCSI 2 standard:
+			//      A parameter list length of zero indicates that no data shall
+			//      be transferred. This condition shall not be considered as an
+			//		error.
+			scsiDev.phase = STATUS;
+		}
+		else
+		{
+			scsiDev.dataLen = len;
+			scsiDev.phase = DATA_OUT;
+			scsiDev.postDataOutHook = doModeSelect;
+		}
+	}
+	else if (command == 0x55)
+	{
+		// MODE SELECT(10)
+		int allocLength = (((uint16_t) scsiDev.cdb[7]) << 8) + scsiDev.cdb[8];
+		if (allocLength == 0)
+		{
+			// If len == 0, then transfer no data. From the SCSI 2 standard:
+			//      A parameter list length of zero indicates that no data shall
+			//      be transferred. This condition shall not be considered as an
+			//		error.
+			scsiDev.phase = STATUS;
+		}
+		else
+		{
+			scsiDev.dataLen = allocLength;
+			scsiDev.phase = DATA_OUT;
+			scsiDev.postDataOutHook = doModeSelect;
+		}
+	}
+	else
+	{
+		commandHandled = 0;
+	}
+
+	return commandHandled;
+}
+

+ 22 - 0
lib/SCSI2SD/src/firmware/mode.h

@@ -0,0 +1,22 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef MODE_H
+#define MODE_H
+
+int scsiModeCommand(void);
+
+#endif

+ 1283 - 0
lib/SCSI2SD/src/firmware/scsi.c

@@ -0,0 +1,1283 @@
+//	Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "scsiPhy.h"
+#include "config.h"
+#include "diagnostic.h"
+#include "disk.h"
+#include "inquiry.h"
+#include "led.h"
+#include "mode.h"
+#include "scsi2sd_time.h"
+#include "bsp.h"
+#include "cdrom.h"
+//#include "debug.h"
+#include "tape.h"
+#include "mo.h"
+#include "vendor.h"
+
+#include <string.h>
+
+// Global SCSI device state.
+ScsiDevice scsiDev S2S_DMA_ALIGN;
+
+static void enter_SelectionPhase(void);
+static void process_SelectionPhase(void);
+static void enter_MessageIn(uint8_t message);
+static void enter_Status(uint8_t status);
+static void enter_DataIn(int len);
+static void process_DataIn(void);
+static void process_DataOut(void);
+static void process_Command(void);
+
+static void doReserveRelease(void);
+
+void enter_BusFree()
+{
+	// This delay probably isn't needed for most SCSI hosts, but it won't
+	// hurt either. It's possible some of the samplers needed this delay.
+	if (scsiDev.compatMode < COMPAT_SCSI2)
+	{
+		s2s_delay_us(2);
+	}
+
+#if 0
+	if (scsiDev.status != GOOD)// && isDebugEnabled())
+	{
+		// We want to capture debug information for failure cases.
+		s2s_delay_ms(80);
+	}
+#endif
+
+
+	scsiEnterBusFree();
+
+	// Wait for the initiator to cease driving signals
+	// Bus settle delay + bus clear delay = 1200ns
+    // Just waiting the clear delay is sufficient.
+	s2s_delay_ns(800);
+
+	s2s_ledOff();
+	scsiDev.phase = BUS_FREE;
+	scsiDev.selFlag = 0;
+}
+
+static void enter_MessageIn(uint8_t message)
+{
+	scsiDev.msgIn = message;
+	scsiDev.phase = MESSAGE_IN;
+}
+
+int process_MessageIn(int releaseBusFree)
+{
+	scsiEnterPhase(MESSAGE_IN);
+	scsiWriteByte(scsiDev.msgIn);
+
+	if (unlikely(scsiDev.atnFlag))
+	{
+		// If there was a parity error, we go
+		// back to MESSAGE_OUT first, get out parity error message, then come
+		// back here.
+		return 0;
+	}
+	else if ((scsiDev.msgIn == MSG_LINKED_COMMAND_COMPLETE) ||
+		(scsiDev.msgIn == MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG))
+	{
+		// Go back to the command phase and start again.
+		scsiDev.phase = COMMAND;
+		scsiDev.dataPtr = 0;
+		scsiDev.savedDataPtr = 0;
+		scsiDev.dataLen = 0;
+		scsiDev.status = GOOD;
+		transfer.blocks = 0;
+		transfer.currentBlock = 0;
+		return 0;
+	}
+	else if (releaseBusFree) /*if (scsiDev.msgIn == MSG_COMMAND_COMPLETE)*/
+	{
+		enter_BusFree();
+		return 1;
+	}
+	else
+	{
+		return 1;
+	}
+}
+
+static void messageReject()
+{
+	scsiEnterPhase(MESSAGE_IN);
+	scsiWriteByte(MSG_REJECT);
+}
+
+static void enter_Status(uint8_t status)
+{
+	scsiDev.status = status;
+	scsiDev.phase = STATUS;
+
+	scsiDev.lastStatus = scsiDev.status;
+	scsiDev.lastSense = scsiDev.target->sense.code;
+	scsiDev.lastSenseASC = scsiDev.target->sense.asc;
+}
+
+void process_Status()
+{
+	scsiEnterPhase(STATUS);
+
+	uint8_t message;
+
+	uint8_t control = scsiDev.cdb[scsiDev.cdbLen - 1];
+
+	if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_OMTI)
+	{
+		// OMTI non-standard LINK control
+		if (control & 0x01)
+		{
+			scsiDev.phase = COMMAND;
+			return;
+		}
+	}
+
+	if ((scsiDev.status == GOOD) && (control & 0x01) &&
+		scsiDev.target->cfg->quirks != S2S_CFG_QUIRKS_XEBEC)
+	{
+		// Linked command.
+		scsiDev.status = INTERMEDIATE;
+		if (control & 0x02)
+		{
+			message = MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG;
+		}
+		else
+		{
+			message = MSG_LINKED_COMMAND_COMPLETE;
+		}
+	}
+	else
+	{
+		message = MSG_COMMAND_COMPLETE;
+	}
+
+	if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
+	{
+		// More non-standardness. Expects 2 status bytes (really status + msg)
+		// 00 d 000 err 0
+		// d == disk number
+		// ERR = 1 if error.
+		if (scsiDev.status == GOOD)
+		{
+			scsiWriteByte(scsiDev.cdb[1] & 0x20);
+		}
+		else
+		{
+			scsiWriteByte((scsiDev.cdb[1] & 0x20) | 0x2);
+		}
+		s2s_delay_us(10); // Seems to need a delay before changing phase bits.
+	}
+	else if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_OMTI)
+	{
+		scsiDev.status |= (scsiDev.target->targetId & 0x03) << 5;
+		scsiWriteByte(scsiDev.status);
+	}
+	else
+	{
+		scsiWriteByte(scsiDev.status);
+	}
+
+	scsiDev.lastStatus = scsiDev.status;
+	scsiDev.lastSense = scsiDev.target->sense.code;
+	scsiDev.lastSenseASC = scsiDev.target->sense.asc;
+
+	// Command Complete occurs AFTER a valid status has been
+	// sent. then we go bus-free.
+	enter_MessageIn(message);
+}
+
+static void enter_DataIn(int len)
+{
+	scsiDev.dataLen = len;
+	scsiDev.phase = DATA_IN;
+}
+
+static void process_DataIn()
+{
+	uint32_t len;
+
+	if (scsiDev.dataLen > sizeof(scsiDev.data))
+	{
+		scsiDev.dataLen = sizeof(scsiDev.data);
+	}
+
+	len = scsiDev.dataLen - scsiDev.dataPtr;
+	if (len > 0)
+	{
+		scsiEnterPhase(DATA_IN);
+		scsiWrite(scsiDev.data + scsiDev.dataPtr, len);
+		scsiDev.dataPtr += len;
+	}
+
+	if ((scsiDev.dataPtr >= scsiDev.dataLen) &&
+		(transfer.currentBlock == transfer.blocks))
+	{
+		enter_Status(GOOD);
+	}
+}
+
+static void process_DataOut()
+{
+	uint32_t len;
+
+	if (scsiDev.dataLen > sizeof(scsiDev.data))
+	{
+		scsiDev.dataLen = sizeof(scsiDev.data);
+	}
+
+	len = scsiDev.dataLen - scsiDev.dataPtr;
+	if (len > 0)
+	{
+		scsiEnterPhase(DATA_OUT);
+
+		int parityError = 0;
+		scsiRead(scsiDev.data + scsiDev.dataPtr, len, &parityError);
+		scsiDev.dataPtr += len;
+
+		if (parityError &&
+			(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
+		{
+			scsiDev.target->sense.code = ABORTED_COMMAND;
+			scsiDev.target->sense.asc = SCSI_PARITY_ERROR;
+			enter_Status(CHECK_CONDITION);
+		}
+	}
+
+	if ((scsiDev.dataPtr >= scsiDev.dataLen) &&
+		(transfer.currentBlock == transfer.blocks))
+	{
+		if (scsiDev.postDataOutHook != NULL)
+		{
+			scsiDev.postDataOutHook();
+		}
+		else
+		{
+			enter_Status(GOOD);
+		}
+	}
+}
+
+static const uint8_t CmdGroupBytes[8] = {6, 10, 10, 6, 6, 12, 6, 6};
+static void process_Command()
+{
+	int group;
+	uint8_t command;
+	uint8_t control;
+
+	scsiEnterPhase(COMMAND);
+
+	memset(scsiDev.cdb + 6, 0, sizeof(scsiDev.cdb) - 6);
+	int parityError = 0;
+	scsiRead(scsiDev.cdb, 6, &parityError);
+
+	group = scsiDev.cdb[0] >> 5;
+	scsiDev.cdbLen = CmdGroupBytes[group];
+	if (parityError &&
+		(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
+	{
+		// Don't try and read more bytes, as we cannot be sure what group
+		// the command should be.
+	}
+	else if (scsiDev.cdbLen - 6 > 0)
+	{
+		scsiRead(scsiDev.cdb + 6, scsiDev.cdbLen - 6, &parityError);
+	}
+	command = scsiDev.cdb[0];
+
+	// Prefer LUN's set by IDENTIFY messages for newer hosts.
+	if (scsiDev.lun < 0)
+	{
+		if (command == 0xE0 || command == 0xE4) // XEBEC s1410
+		{
+			scsiDev.lun = 0;
+		}
+		else
+		{
+			scsiDev.lun = scsiDev.cdb[1] >> 5;
+		}
+	}
+
+
+	// For Philips P2000C with Xebec S1410 SASI/MFM adapter
+	// http://bitsavers.trailing-edge.com/pdf/xebec/104524C_S1410Man_Aug83.pdf
+	if ((scsiDev.lun > 0) && (scsiDev.boardCfg.flags & S2S_CFG_MAP_LUNS_TO_IDS))
+	{
+		int tgtIndex;
+		for (tgtIndex = 0; tgtIndex < S2S_MAX_TARGETS; ++tgtIndex)
+		{
+			if (scsiDev.targets[tgtIndex].targetId == scsiDev.lun)
+			{
+				scsiDev.target = &scsiDev.targets[tgtIndex];
+				scsiDev.lun = 0;
+				break;
+			}
+		}
+	}
+
+	control = scsiDev.cdb[scsiDev.cdbLen - 1];
+
+	scsiDev.cmdCount++;
+	const S2S_TargetCfg* cfg = scsiDev.target->cfg;
+
+	if (unlikely(scsiDev.resetFlag))
+	{
+		// Don't log bogus commands
+		scsiDev.cmdCount--;
+		memset(scsiDev.cdb, 0xff, sizeof(scsiDev.cdb));
+		return;
+	}
+	else if (parityError &&
+		(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
+	{
+		scsiDev.target->sense.code = ABORTED_COMMAND;
+		scsiDev.target->sense.asc = SCSI_PARITY_ERROR;
+		enter_Status(CHECK_CONDITION);
+	}
+	else if ((control & 0x02) && ((control & 0x01) == 0) &&
+		// used for head step options on xebec.
+		likely(scsiDev.target->cfg->quirks != S2S_CFG_QUIRKS_XEBEC))
+	{
+		// FLAG set without LINK flag.
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		enter_Status(CHECK_CONDITION);
+	}
+	else if (command == 0x12)
+	{
+		s2s_scsiInquiry();
+	}
+	else if (command == 0x03)
+	{
+		// REQUEST SENSE
+		uint32_t allocLength = scsiDev.cdb[4];
+
+		if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
+		{
+			// Completely non-standard
+			allocLength = 4;
+			if (scsiDev.target->sense.code == NO_SENSE)
+				scsiDev.data[0] = 0;
+			else if (scsiDev.target->sense.code == ILLEGAL_REQUEST)
+				scsiDev.data[0] = 0x20; // Illegal command
+			else if (scsiDev.target->sense.code == NOT_READY)
+				scsiDev.data[0] = 0x04; // Drive not ready
+			else
+				scsiDev.data[0] = 0x11;  // Uncorrectable data error
+
+			scsiDev.data[1] = (scsiDev.cdb[1] & 0x20) | ((transfer.lba >> 16) & 0x1F);
+			scsiDev.data[2] = transfer.lba >> 8;
+			scsiDev.data[3] = transfer.lba;
+		}
+		else
+		{
+			// As specified by the SASI and SCSI1 standard.
+			// Newer initiators won't be specifying 0 anyway.
+			if (allocLength == 0) allocLength = 4;
+
+			memset(scsiDev.data, 0, 256); // Max possible alloc length
+			scsiDev.data[0] = 0xF0;
+			scsiDev.data[2] = scsiDev.target->sense.code & 0x0F;
+
+			scsiDev.data[3] = transfer.lba >> 24;
+			scsiDev.data[4] = transfer.lba >> 16;
+			scsiDev.data[5] = transfer.lba >> 8;
+			scsiDev.data[6] = transfer.lba;
+
+			// Additional bytes if there are errors to report
+			scsiDev.data[7] = 10; // additional length
+			scsiDev.data[12] = scsiDev.target->sense.asc >> 8;
+			scsiDev.data[13] = scsiDev.target->sense.asc;
+		}
+
+		// Silently truncate results. SCSI-2 spec 8.2.14.
+		enter_DataIn(allocLength);
+
+		// This is a good time to clear out old sense information.
+		scsiDev.target->sense.code = NO_SENSE;
+		scsiDev.target->sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
+	}
+	// Some old SCSI drivers do NOT properly support
+	// unitAttention. eg. the Mac Plus would trigger a SCSI reset
+	// on receiving the unit attention response on boot, thus
+	// triggering another unit attention condition.
+	else if (scsiDev.target->unitAttention &&
+		(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_UNIT_ATTENTION))
+	{
+		scsiDev.target->sense.code = UNIT_ATTENTION;
+		scsiDev.target->sense.asc = scsiDev.target->unitAttention;
+
+		// If initiator doesn't do REQUEST SENSE for the next command, then
+		// data is lost.
+		scsiDev.target->unitAttention = 0;
+
+		enter_Status(CHECK_CONDITION);
+	}
+	else if (scsiDev.lun)
+	{
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = LOGICAL_UNIT_NOT_SUPPORTED;
+		enter_Status(CHECK_CONDITION);
+	}
+	else if (command == 0x17 || command == 0x16)
+	{
+		doReserveRelease();
+	}
+	else if ((scsiDev.target->reservedId >= 0) &&
+		(scsiDev.target->reservedId != scsiDev.initiatorId))
+	{
+		enter_Status(CONFLICT);
+	}
+	// Handle odd device types first that may override basic read and
+	// write commands. Will fall-through to generic disk handling.
+	else if (((cfg->deviceType == S2S_CFG_OPTICAL) && scsiCDRomCommand()) ||
+		((cfg->deviceType == S2S_CFG_SEQUENTIAL) && scsiTapeCommand()) ||
+		((cfg->deviceType == S2S_CFG_MO) && scsiMOCommand()))
+	{
+		// Already handled.
+	}
+	else if (scsiDiskCommand())
+	{
+		// Already handled.
+		// check for the performance-critical read/write
+		// commands ASAP.
+	}
+	else if (command == 0x1C)
+	{
+		scsiReceiveDiagnostic();
+	}
+	else if (command == 0x1D)
+	{
+		scsiSendDiagnostic();
+	}
+	else if (command == 0x3B)
+	{
+		scsiWriteBuffer();
+	}
+	else if (command == 0x0f &&
+		scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
+	{
+		scsiWriteSectorBuffer();
+	}
+	else if (command == 0x3C)
+	{
+		scsiReadBuffer();
+	}
+	else if (!scsiModeCommand() && !scsiVendorCommand())
+	{
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_COMMAND_OPERATION_CODE;
+		enter_Status(CHECK_CONDITION);
+	}
+
+	// Successful
+	if (scsiDev.phase == COMMAND) // No status set, and not in DATA_IN
+	{
+		enter_Status(GOOD);
+	}
+
+}
+
+static void doReserveRelease()
+{
+	int extentReservation = scsiDev.cdb[1] & 1;
+	int thirdPty = scsiDev.cdb[1] & 0x10;
+	int thirdPtyId = (scsiDev.cdb[1] >> 1) & 0x7;
+	uint8_t command = scsiDev.cdb[0];
+
+	int canRelease =
+		(!thirdPty && (scsiDev.initiatorId == scsiDev.target->reservedId)) ||
+			(thirdPty &&
+				(scsiDev.target->reserverId == scsiDev.initiatorId) &&
+				(scsiDev.target->reservedId == thirdPtyId)
+			);
+
+	if (extentReservation)
+	{
+		// Not supported.
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		enter_Status(CHECK_CONDITION);
+	}
+	else if (command == 0x17) // release
+	{
+		if ((scsiDev.target->reservedId < 0) || canRelease)
+		{
+			scsiDev.target->reservedId = -1;
+			scsiDev.target->reserverId = -1;
+		}
+		else
+		{
+			enter_Status(CONFLICT);
+		}
+	}
+	else // assume reserve.
+	{
+		if ((scsiDev.target->reservedId < 0) || canRelease)
+		{
+			scsiDev.target->reserverId = scsiDev.initiatorId;
+			if (thirdPty)
+			{
+				scsiDev.target->reservedId = thirdPtyId;
+			}
+			else
+			{
+				scsiDev.target->reservedId = scsiDev.initiatorId;
+			}
+		}
+		else
+		{
+			// Already reserved by someone else!
+			enter_Status(CONFLICT);
+		}
+	}
+}
+
+static uint32_t resetUntil = 0;
+
+static void scsiReset()
+{
+	scsiDev.rstCount++;
+	s2s_ledOff();
+
+	scsiPhyReset();
+
+	scsiDev.phase = BUS_FREE;
+	scsiDev.atnFlag = 0;
+	scsiDev.resetFlag = 0;
+	scsiDev.selFlag = 0;
+	scsiDev.lun = -1;
+	scsiDev.compatMode = COMPAT_UNKNOWN;
+
+	if (scsiDev.target)
+	{
+		if (scsiDev.target->unitAttention != POWER_ON_RESET)
+		{
+			scsiDev.target->unitAttention = SCSI_BUS_RESET;
+		}
+		scsiDev.target->reservedId = -1;
+		scsiDev.target->reserverId = -1;
+		scsiDev.target->sense.code = NO_SENSE;
+		scsiDev.target->sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
+	}
+	scsiDev.target = NULL;
+
+	for (int i = 0; i < S2S_MAX_TARGETS; ++i)
+	{
+		scsiDev.targets[i].syncOffset = 0;
+		scsiDev.targets[i].syncPeriod = 0;
+	}
+	scsiDev.minSyncPeriod = 0;
+
+	scsiDiskReset();
+
+	scsiDev.postDataOutHook = NULL;
+
+	scsiDev.sdUnderrunCount = 0;
+
+	// Sleep to allow the bus to settle down a bit.
+	// We must be ready again within the "Reset to selection time" of
+	// 250ms.
+	// There is no guarantee that the RST line will be negated by then.
+	// NOTE: We could be connected and powered by USB for configuration,
+	// in which case TERMPWR cannot be supplied, and reset will ALWAYS
+	// be true. Therefore, the sleep here must be slow to avoid slowing
+	// USB comms
+	resetUntil = s2s_getTime_ms() + 2; // At least 1ms.
+}
+
+static void enter_SelectionPhase()
+{
+	// Ignore stale versions of this flag, but ensure we know the
+	// current value if the flag is still set.
+	scsiDev.atnFlag = 0;
+	scsiDev.dataPtr = 0;
+	scsiDev.savedDataPtr = 0;
+	scsiDev.dataLen = 0;
+	scsiDev.status = GOOD;
+	scsiDev.phase = SELECTION;
+	scsiDev.lun = -1;
+	scsiDev.discPriv = 0;
+
+	scsiDev.initiatorId = -1;
+	scsiDev.target = NULL;
+
+	transfer.blocks = 0;
+	transfer.currentBlock = 0;
+
+	scsiDev.postDataOutHook = NULL;
+
+	scsiDev.needSyncNegotiationAck = 0;
+}
+
+static void process_SelectionPhase()
+{
+	// Selection delays.
+	// Many SCSI1 samplers that use a 5380 chip need a delay of at least 1ms.
+	// The Mac Plus boot-time (ie. rom code) selection abort time
+	// is < 1ms and must have no delay (standard suggests 250ms abort time)
+	// Most newer SCSI2 hosts don't care either way.
+	if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
+	{
+		s2s_delay_ms(1); // Simply won't work if set to 0.
+	}
+	else if (scsiDev.boardCfg.selectionDelay == 255) // auto
+	{
+		if (scsiDev.compatMode < COMPAT_SCSI2)
+		{
+			s2s_delay_ms(1);
+		}
+	}
+	else if (scsiDev.boardCfg.selectionDelay != 0)
+	{
+		s2s_delay_ms(scsiDev.boardCfg.selectionDelay);
+	}
+
+	uint8_t selStatus = *SCSI_STS_SELECTED;
+	if ((selStatus == 0) && (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_SEL_LATCH))
+	{
+		selStatus = scsiDev.selFlag;
+	}
+
+	int tgtIndex;
+	TargetState* target = NULL;
+	for (tgtIndex = 0; tgtIndex < S2S_MAX_TARGETS; ++tgtIndex)
+	{
+		if (scsiDev.targets[tgtIndex].targetId == (selStatus & 7))
+		{
+			target = &scsiDev.targets[tgtIndex];
+			break;
+		}
+	}
+	if ((target != NULL) && (selStatus & 0x40))
+	{
+		// We've been selected!
+		// Assert BSY - Selection success!
+		// must happen within 200us (Selection abort time) of seeing our
+		// ID + SEL.
+		// (Note: the initiator will be waiting the "Selection time-out delay"
+		// for our BSY response, which is actually a very generous 250ms)
+		*SCSI_CTRL_BSY = 1;
+		s2s_ledOn();
+
+		scsiDev.target = target;
+
+		// Do we enter MESSAGE OUT immediately ? SCSI 1 and 2 standards says
+		// move to MESSAGE OUT if ATN is true before we assert BSY.
+		// The initiator should assert ATN with SEL.
+		scsiDev.atnFlag = selStatus & 0x80;
+
+
+		// Unit attention breaks many older SCSI hosts. Disable it completely
+		// for SCSI-1 (and older) hosts, regardless of our configured setting.
+		// Enable the compatability mode also as many SASI and SCSI1
+		// controllers don't generate parity bits.
+		if (!scsiDev.atnFlag)
+		{
+			target->unitAttention = 0;
+			scsiDev.compatMode = COMPAT_SCSI1;
+		}
+		else if (!(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_SCSI2))
+		{
+			scsiDev.compatMode = COMPAT_SCSI2_DISABLED;
+		}
+		else
+		{
+			scsiDev.compatMode = COMPAT_SCSI2;
+		}
+
+		scsiDev.selCount++;
+
+
+		// Save our initiator now that we're no longer in a time-critical
+		// section.
+		// SCSI1/SASI initiators may not set their own ID.
+		scsiDev.initiatorId = (selStatus >> 3) & 0x7;
+
+		// Wait until the end of the selection phase.
+		uint32_t selTimerBegin = s2s_getTime_ms();
+		while (likely(!scsiDev.resetFlag))
+		{
+			if (!scsiStatusSEL())
+			{
+				break;
+			}
+			else if (s2s_elapsedTime_ms(selTimerBegin) >= 10 &&
+				scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
+			{
+				// XEBEC hosts may not bother releasing SEL at all until
+				// just before the command ends.
+				break;
+			}
+			else if (s2s_elapsedTime_ms(selTimerBegin) >= 250)
+			{
+				*SCSI_CTRL_BSY = 0;
+				scsiDev.resetFlag = 1;
+				break;
+			}
+		}
+
+		scsiDev.phase = COMMAND;
+	}
+	else if (!selStatus)
+	{
+		scsiDev.phase = BUS_BUSY;
+	}
+	scsiDev.selFlag = 0;
+}
+
+static void process_MessageOut()
+{
+	int wasNeedSyncNegotiationAck = scsiDev.needSyncNegotiationAck;
+	scsiDev.needSyncNegotiationAck = 0; // Successful on -most- messages.
+
+	scsiEnterPhase(MESSAGE_OUT);
+
+	scsiDev.atnFlag = 0;
+	scsiDev.msgOut = scsiReadByte();
+	scsiDev.msgCount++;
+
+	if (scsiParityError() &&
+		(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
+	{
+		// Skip the remaining message bytes, and then start the MESSAGE_OUT
+		// phase again from the start. The initiator will re-send the
+		// same set of messages.
+		while (scsiStatusATN() && !scsiDev.resetFlag)
+		{
+			scsiReadByte();
+		}
+
+		// Go-back and try the message again.
+		scsiDev.atnFlag = 1;
+	}
+	else if (scsiDev.msgOut == 0x00)
+	{
+		// COMMAND COMPLETE. but why would the target be receiving this ? nfi.
+		enter_BusFree();
+	}
+	else if (scsiDev.msgOut == 0x06)
+	{
+		// ABORT
+		scsiDiskReset();
+		enter_BusFree();
+	}
+	else if (scsiDev.msgOut == 0x0C)
+	{
+		// BUS DEVICE RESET
+
+		scsiDiskReset();
+
+		scsiDev.target->unitAttention = SCSI_BUS_RESET;
+
+		// ANY initiator can reset the reservation state via this message.
+		scsiDev.target->reservedId = -1;
+		scsiDev.target->reserverId = -1;
+
+		// Cancel any sync negotiation
+		scsiDev.target->syncOffset = 0;
+		scsiDev.target->syncPeriod = 0;
+
+		enter_BusFree();
+	}
+	else if (scsiDev.msgOut == 0x05)
+	{
+		// Initiate Detected Error
+		// Ignore for now
+	}
+	else if (scsiDev.msgOut == 0x0F)
+	{
+		// INITIATE RECOVERY
+		// Ignore for now
+	}
+	else if (scsiDev.msgOut == 0x10)
+	{
+		// RELEASE RECOVERY
+		// Ignore for now
+		enter_BusFree();
+	}
+	else if (scsiDev.msgOut == MSG_REJECT)
+	{
+		// Message Reject
+		// Oh well.
+
+		if (wasNeedSyncNegotiationAck)
+		{
+			scsiDev.target->syncOffset = 0;
+			scsiDev.target->syncPeriod = 0;
+		}
+	}
+	else if (scsiDev.msgOut == 0x08)
+	{
+		// NOP
+	}
+	else if (scsiDev.msgOut == 0x09)
+	{
+		// Message Parity Error
+		// Go back and re-send the last message.
+		scsiDev.phase = MESSAGE_IN;
+
+		if (wasNeedSyncNegotiationAck)
+		{
+			scsiDev.target->syncOffset = 0;
+			scsiDev.target->syncPeriod = 0;
+		}
+	}
+	else if (scsiDev.msgOut & 0x80) // 0x80 -> 0xFF
+	{
+		// IDENTIFY
+		if ((scsiDev.msgOut & 0x18) || // Reserved bits set.
+			(scsiDev.msgOut & 0x20))  // We don't have any target routines!
+		{
+			messageReject();
+		}
+
+		scsiDev.lun = scsiDev.msgOut & 0x7;
+		scsiDev.discPriv = 
+			((scsiDev.msgOut & 0x40) && (scsiDev.initiatorId >= 0))
+				? 1 : 0;
+	}
+	else if (scsiDev.msgOut >= 0x20 && scsiDev.msgOut <= 0x2F)
+	{
+		// Two byte message. We don't support these. read and discard.
+		scsiReadByte();
+
+		if (scsiDev.msgOut == 0x23) {
+			// Ignore Wide Residue. We're only 8 bit anyway.
+		} else {
+			messageReject();
+		}
+	}
+	else if (scsiDev.msgOut == 0x01)
+	{
+		int i;
+
+		// Extended message.
+		int msgLen = scsiReadByte();
+		if (msgLen == 0) msgLen = 256;
+		uint8_t extmsg[256];
+		for (i = 0; i < msgLen && !scsiDev.resetFlag; ++i)
+		{
+			// Discard bytes.
+			extmsg[i] = scsiReadByte();
+		}
+
+		if (extmsg[0] == 3 && msgLen == 2) // Wide Data Request
+		{
+			// Negotiate down to 8bit
+			scsiEnterPhase(MESSAGE_IN);
+			static const uint8_t WDTR[] = {0x01, 0x02, 0x03, 0x00};
+			scsiWrite(WDTR, sizeof(WDTR));
+
+			// SDTR becomes invalidated.
+			scsiDev.target->syncOffset = 0;
+			scsiDev.target->syncPeriod = 0;
+		}
+		else if (extmsg[0] == 1 && msgLen == 3) // Synchronous data request
+		{
+			int oldPeriod = scsiDev.target->syncPeriod;
+			int oldOffset = scsiDev.target->syncOffset;
+
+			int transferPeriod = extmsg[1];
+			int offset = extmsg[2];
+
+			if ((
+					(transferPeriod > 0) &&
+					(transferPeriod < scsiDev.minSyncPeriod)) ||
+				(scsiDev.minSyncPeriod == 0))
+			{
+				scsiDev.minSyncPeriod = transferPeriod;
+			}
+
+			if ((transferPeriod > 80) || // 320ns, 3.125MB/s
+				// Amiga A590 (WD33C93 chip) only does 3.5MB/s sync
+				// After 80 we start to run out of bits in the fpga timing
+				// register.
+				(transferPeriod == 0) ||
+				(offset == 0) ||
+				((scsiDev.boardCfg.scsiSpeed != S2S_CFG_SPEED_NoLimit) &&
+					(scsiDev.boardCfg.scsiSpeed <= S2S_CFG_SPEED_ASYNC_50)))
+			{
+				scsiDev.target->syncOffset = 0;
+				scsiDev.target->syncPeriod = 0;
+			} else {
+				scsiDev.target->syncOffset = offset <= 15 ? offset : 15;
+				// FAST20 / 50ns / 20MHz is disabled for now due to
+				// data corruption while reading data. We can count the
+				// ACK's correctly, but can't save the data to a register
+				// before it changes. (ie. transferPeriod == 12)
+				if ((scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_TURBO) &&
+					(transferPeriod <= 16))
+				{
+					scsiDev.target->syncPeriod = 16; // 15.6MB/s
+				}
+				else if (scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_TURBO)
+				{
+					scsiDev.target->syncPeriod = transferPeriod;
+				}
+				else if (transferPeriod <= 25 &&
+					((scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_NoLimit) ||
+						(scsiDev.boardCfg.scsiSpeed >= S2S_CFG_SPEED_SYNC_10)))
+				{
+					scsiDev.target->syncPeriod = 25; // 100ns, 10MB/s
+
+				} else if (transferPeriod < 50 &&
+					((scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_NoLimit) ||
+						(scsiDev.boardCfg.scsiSpeed >= S2S_CFG_SPEED_SYNC_10)))
+				{
+					scsiDev.target->syncPeriod = transferPeriod;
+				} else if (transferPeriod >= 50)
+				{
+					scsiDev.target->syncPeriod = transferPeriod;
+				} else {
+					scsiDev.target->syncPeriod = 50;
+				}
+			}
+
+			if (transferPeriod != oldPeriod ||
+				scsiDev.target->syncPeriod != oldPeriod ||
+				offset != oldOffset ||
+				scsiDev.target->syncOffset != oldOffset ||
+				!wasNeedSyncNegotiationAck) // Don't get into infinite loops negotiating.
+			{
+				scsiEnterPhase(MESSAGE_IN);
+				uint8_t SDTR[] = {0x01, 0x03, 0x01, scsiDev.target->syncPeriod, scsiDev.target->syncOffset};
+				scsiWrite(SDTR, sizeof(SDTR));
+				scsiDev.needSyncNegotiationAck = 1; // Check if this message is rejected.
+				scsiDev.sdUnderrunCount = 0;  // reset counter, may work now.
+
+				// Set to the theoretical speed, then adjust if we measure lower
+				// actual speeds.
+				scsiDev.hostSpeedKBs = s2s_getScsiRateKBs();
+				scsiDev.hostSpeedMeasured = 0;
+			}
+		}
+		else
+		{
+			// Not supported
+			messageReject();
+		}
+	}
+	else
+	{
+		messageReject();
+	}
+
+	// Re-check the ATN flag in case it stays asserted.
+	scsiDev.atnFlag |= scsiStatusATN();
+
+	if (!scsiDev.atnFlag)
+	{
+		// Message wasn't rejected!
+		scsiDev.needSyncNegotiationAck = 0;
+	}
+}
+
+void scsiPoll(void)
+{
+	if (resetUntil != 0 && resetUntil > s2s_getTime_ms())
+	{
+		return;
+	}
+	resetUntil = 0;
+
+	if (unlikely(scsiDev.resetFlag))
+	{
+		scsiReset();
+		// Still in reset phase for a few ms.
+		// Do not try and process any commands.
+		return;
+	}
+
+	switch (scsiDev.phase)
+	{
+	case BUS_FREE:
+		if (scsiStatusBSY())
+		{
+			scsiDev.phase = BUS_BUSY;
+		}
+		// The Arbitration phase is optional for SCSI1/SASI hosts if there is only
+		// one initiator in the chain. Support this by moving
+		// straight to selection if SEL is asserted.
+		// ie. the initiator won't assert BSY and it's own ID before moving to selection.
+		else if (scsiDev.selFlag || *SCSI_STS_SELECTED)
+		{
+			enter_SelectionPhase();
+		}
+	break;
+
+	case BUS_BUSY:
+		// Someone is using the bus. Perhaps they are trying to
+		// select us.
+		if (scsiDev.selFlag || *SCSI_STS_SELECTED)
+		{
+			enter_SelectionPhase();
+		}
+		else if (!scsiStatusBSY())
+		{
+			scsiDev.phase = BUS_FREE;
+		}
+	break;
+
+	case ARBITRATION:
+		// TODO Support reselection.
+		break;
+
+	case SELECTION:
+		process_SelectionPhase();
+	break;
+
+	case RESELECTION:
+		// Not currently supported!
+	break;
+
+	case COMMAND:
+		// Do not check ATN here. SCSI 1 & 2 initiators must set ATN
+		// and SEL together upon entering the selection phase if they
+		// want to send a message (IDENTIFY) immediately.
+		if (scsiDev.atnFlag)
+		{
+			process_MessageOut();
+		}
+		else
+		{
+			process_Command();
+		}
+	break;
+
+	case DATA_IN:
+		scsiDev.atnFlag |= scsiStatusATN();
+		if (scsiDev.atnFlag)
+		{
+			process_MessageOut();
+		}
+		else
+		{
+			process_DataIn();
+		}
+	break;
+
+	case DATA_OUT:
+		scsiDev.atnFlag |= scsiStatusATN();
+		if (scsiDev.atnFlag)
+		{
+			process_MessageOut();
+		}
+		else
+		{
+			process_DataOut();
+		}
+	break;
+
+	case STATUS:
+		scsiDev.atnFlag |= scsiStatusATN();
+		if (scsiDev.atnFlag)
+		{
+			process_MessageOut();
+		}
+		else
+		{
+			process_Status();
+		}
+	break;
+
+	case MESSAGE_IN:
+		scsiDev.atnFlag |= scsiStatusATN();
+		if (scsiDev.atnFlag)
+		{
+			process_MessageOut();
+		}
+		else
+		{
+			process_MessageIn(1);
+		}
+
+	break;
+
+	case MESSAGE_OUT:
+		process_MessageOut();
+	break;
+	}
+}
+
+void scsiInit()
+{
+	static int firstInit = 1;
+
+	scsiDev.atnFlag = 0;
+	scsiDev.resetFlag = 1;
+	scsiDev.selFlag = 0;
+	scsiDev.phase = BUS_FREE;
+	scsiDev.target = NULL;
+	scsiDev.compatMode = COMPAT_UNKNOWN;
+	scsiDev.hostSpeedKBs = 0;
+	scsiDev.hostSpeedMeasured = 0;
+
+	int i;
+	for (i = 0; i < S2S_MAX_TARGETS; ++i)
+	{
+		const S2S_TargetCfg* cfg = s2s_getConfigByIndex(i);
+		if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
+		{
+			scsiDev.targets[i].targetId = cfg->scsiId & S2S_CFG_TARGET_ID_BITS;
+			scsiDev.targets[i].cfg = cfg;
+
+			scsiDev.targets[i].liveCfg.bytesPerSector = cfg->bytesPerSector;
+		}
+		else
+		{
+			scsiDev.targets[i].targetId = 0xff;
+			scsiDev.targets[i].cfg = NULL;
+		}
+		scsiDev.targets[i].reservedId = -1;
+		scsiDev.targets[i].reserverId = -1;
+		if (firstInit)
+		{
+			scsiDev.targets[i].unitAttention = POWER_ON_RESET;
+		}
+		else
+		{
+			scsiDev.targets[i].unitAttention = PARAMETERS_CHANGED;
+		}
+		scsiDev.targets[i].sense.code = NO_SENSE;
+		scsiDev.targets[i].sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
+
+		scsiDev.targets[i].syncOffset = 0;
+		scsiDev.targets[i].syncPeriod = 0;
+
+		// Always "start" the device. Many systems (eg. Apple System 7)
+		// won't respond properly to
+		// LOGICAL_UNIT_NOT_READY_INITIALIZING_COMMAND_REQUIRED sense
+		// code
+		scsiDev.targets[i].started = 1;
+	}
+	firstInit = 0;
+}
+
+/* TODO REENABLE
+void scsiDisconnect()
+{
+	scsiEnterPhase(MESSAGE_IN);
+	scsiWriteByte(0x02); // save data pointer
+	scsiWriteByte(0x04); // disconnect msg.
+
+	// For now, the caller is responsible for tracking the disconnected
+	// state, and calling scsiReconnect.
+	// Ideally the client would exit their loop and we'd implement this
+	// as part of scsiPoll
+	int phase = scsiDev.phase;
+	enter_BusFree();
+	scsiDev.phase = phase;
+}
+*/
+
+/* TODO REENABLE
+int scsiReconnect()
+{
+	int reconnected = 0;
+
+	int sel = SCSI_ReadFilt(SCSI_Filt_SEL);
+	int bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
+	if (!sel && !bsy)
+	{
+		s2s_delay_us(1);
+		sel = SCSI_ReadFilt(SCSI_Filt_SEL);
+		bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
+	}
+
+	if (!sel && !bsy)
+	{
+		// Arbitrate.
+		s2s_ledOn();
+		uint8_t scsiIdMask = 1 << scsiDev.target->targetId;
+		SCSI_Out_Bits_Write(scsiIdMask);
+		SCSI_Out_Ctl_Write(1); // Write bits manually.
+		SCSI_SetPin(SCSI_Out_BSY);
+
+		s2s_delay_us(3); // arbitrate delay. 2.4us.
+
+		uint8_t dbx = scsiReadDBxPins();
+		sel = SCSI_ReadFilt(SCSI_Filt_SEL);
+		if (sel || ((dbx ^ scsiIdMask) > scsiIdMask))
+		{
+			// Lost arbitration.
+			SCSI_Out_Ctl_Write(0);
+			SCSI_ClearPin(SCSI_Out_BSY);
+			s2s_ledOff();
+		}
+		else
+		{
+			// Won arbitration
+			SCSI_SetPin(SCSI_Out_SEL);
+			s2s_delay_us(1); // Bus clear + Bus settle.
+
+			// Reselection phase
+			SCSI_CTL_PHASE_Write(__scsiphase_io);
+			SCSI_Out_Bits_Write(scsiIdMask | (1 << scsiDev.initiatorId));
+			scsiDeskewDelay(); // 2 deskew delays
+			scsiDeskewDelay(); // 2 deskew delays
+			SCSI_ClearPin(SCSI_Out_BSY);
+			s2s_delay_us(1);  // Bus Settle Delay
+
+			uint32_t waitStart_ms = getTime_ms();
+			bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
+			// Wait for initiator.
+			while (
+				!bsy &&
+				!scsiDev.resetFlag &&
+				(elapsedTime_ms(waitStart_ms) < 250))
+			{
+				bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
+			}
+
+			if (bsy)
+			{
+				SCSI_SetPin(SCSI_Out_BSY);
+				scsiDeskewDelay(); // 2 deskew delays
+				scsiDeskewDelay(); // 2 deskew delays
+				SCSI_ClearPin(SCSI_Out_SEL);
+
+				// Prepare for the initial IDENTIFY message.
+				SCSI_Out_Ctl_Write(0);
+				scsiEnterPhase(MESSAGE_IN);
+
+				// Send identify command
+				scsiWriteByte(0x80);
+
+				scsiEnterPhase(scsiDev.phase);
+				reconnected = 1;
+			}
+			else
+			{
+				// reselect timeout.
+				SCSI_Out_Ctl_Write(0);
+				SCSI_ClearPin(SCSI_Out_SEL);
+				SCSI_CTL_PHASE_Write(0);
+				s2s_ledOff();
+			}
+		}
+	}
+	return reconnected;
+}
+*/
+

+ 202 - 0
lib/SCSI2SD/src/firmware/scsi.h

@@ -0,0 +1,202 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef SCSI_H
+#define SCSI_H
+
+#include "geometry.h"
+#include "sense.h"
+
+#include <stdint.h>
+
+typedef enum
+{
+	// internal bits
+	__scsiphase_msg = 1,
+	__scsiphase_cd = 2,
+	__scsiphase_io = 4,
+
+	BUS_FREE = -1,
+	BUS_BUSY = -2,
+	ARBITRATION = -3,
+	SELECTION = -4,
+	RESELECTION = -5,
+	STATUS = __scsiphase_cd | __scsiphase_io,
+	COMMAND = __scsiphase_cd,
+	DATA_IN = __scsiphase_io,
+	DATA_OUT = 0,
+	MESSAGE_IN = __scsiphase_msg | __scsiphase_cd | __scsiphase_io,
+	MESSAGE_OUT = __scsiphase_msg | __scsiphase_cd
+} SCSI_PHASE;
+
+typedef enum
+{
+	GOOD = 0,
+	CHECK_CONDITION = 2,
+	BUSY = 0x8,
+	INTERMEDIATE = 0x10,
+	CONFLICT = 0x18
+} SCSI_STATUS;
+
+typedef enum
+{
+	MSG_COMMAND_COMPLETE = 0,
+	MSG_REJECT = 0x7,
+	MSG_LINKED_COMMAND_COMPLETE = 0x0A,
+	MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG = 0x0B
+} SCSI_MESSAGE;
+
+typedef enum
+{
+	COMPAT_UNKNOWN,
+	COMPAT_SCSI1,
+
+	// Messages are being used, yet SCSI 2 mode is disabled.
+	// This impacts interpretation of INQUIRY commands.
+	COMPAT_SCSI2_DISABLED,
+
+	COMPAT_SCSI2
+} SCSI_COMPAT_MODE;
+
+// Maximum value for bytes-per-sector.
+#ifndef MAX_SECTOR_SIZE
+#define MAX_SECTOR_SIZE 8192
+#endif
+
+#ifndef MIN_SECTOR_SIZE
+#define MIN_SECTOR_SIZE 64
+#endif
+
+#ifndef SCSI2SD_BUFFER_SIZE
+#define SCSI2SD_BUFFER_SIZE (MAX_SECTOR_SIZE * 8)
+#endif
+
+// Shadow parameters, possibly not saved to flash yet.
+// Set via Mode Select
+typedef struct
+{
+	uint16_t bytesPerSector;
+} LiveCfg;
+
+typedef struct
+{
+	uint8_t targetId;
+
+	const S2S_TargetCfg* cfg;
+
+	LiveCfg liveCfg;
+
+	ScsiSense sense;
+
+	uint16_t unitAttention; // Set to the sense qualifier key to be returned.
+
+	// Only let the reserved initiator talk to us.
+	// A 3rd party may be sending the RESERVE/RELEASE commands
+	int reservedId; // 0 -> 7 if reserved. -1 if not reserved.
+	int reserverId; // 0 -> 7 if reserved. -1 if not reserved.
+
+	uint8_t syncOffset;
+	uint8_t syncPeriod;
+
+	uint8_t started; // Controlled by START STOP UNIT
+} TargetState;
+
+typedef struct
+{
+	// TODO reduce this buffer size and add a proper cache
+	// Must be aligned for DMA
+	// 65536 bytes is the DMA limit
+	uint8_t data[SCSI2SD_BUFFER_SIZE];
+
+	TargetState targets[S2S_MAX_TARGETS];
+	TargetState* target;
+	S2S_BoardCfg boardCfg;
+
+
+	// Set to true (1) if the ATN flag was set, and we need to
+	// enter the MESSAGE_OUT phase.
+	int atnFlag;
+
+	// Set to true (1) if the RST flag was set.
+	volatile int resetFlag;
+
+	// Set to sel register if the SEL flag was set.
+	volatile int selFlag;
+
+	// Set to true (1) if a parity error was observed.
+	int parityError;
+
+	int phase;
+
+	int dataPtr; // Index into data, reset on [re]selection to savedDataPtr
+	int savedDataPtr; // Index into data, initially 0.
+	int dataLen;
+
+	uint8_t cdb[12]; // command descriptor block
+	uint8_t cdbLen; // 6, 10, or 12 byte message.
+	int8_t lun; // Target lun, set by IDENTIFY message.
+	uint8_t discPriv; // Disconnect priviledge.
+	uint8_t compatMode; // SCSI_COMPAT_MODE
+
+	// Only let the reserved initiator talk to us.
+	// A 3rd party may be sending the RESERVE/RELEASE commands
+	int initiatorId; // 0 -> 7. Set during the selection phase.
+
+	// SCSI_STATUS value.
+	// Change to CHECK_CONDITION when setting a SENSE value
+	uint8_t status;
+
+	uint8_t msgIn;
+	uint8_t msgOut;
+
+	void (*postDataOutHook)(void);
+
+	uint8_t cmdCount;
+	uint8_t selCount;
+	uint8_t rstCount;
+	uint8_t msgCount;
+	uint8_t watchdogTick;
+	uint8_t lastStatus;
+	uint8_t lastSense;
+	uint16_t lastSenseASC;
+	uint8_t minSyncPeriod; // Debug use only.
+
+	int needSyncNegotiationAck;
+	int sdUnderrunCount;
+
+	// Estimate of the SCSI host actual speed
+	uint32_t hostSpeedKBs;
+	int hostSpeedMeasured;
+} ScsiDevice;
+
+extern ScsiDevice scsiDev;
+
+void process_Status(void);
+int process_MessageIn(int releaseBusFree);
+void enter_BusFree(void);
+
+void scsiInit(void);
+void scsiPoll(void);
+void scsiDisconnect(void);
+int scsiReconnect(void);
+
+
+// Utility macros, consistent with the Linux Kernel code.
+#define likely(x)       __builtin_expect(!!(x), 1)
+#define unlikely(x)     __builtin_expect(!!(x), 0)
+//#define likely(x)       (x)
+//#define unlikely(x)     (x)
+#endif

+ 48 - 0
lib/SCSI2SD/src/firmware/sd.h

@@ -0,0 +1,48 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef S2S_SD_H
+#define S2S_SD_H
+
+#include <stdint.h>
+
+#define SD_SECTOR_SIZE 512
+
+typedef struct
+{
+	int version; // SDHC = version 2.
+	uint32_t capacity; // in 512 byte blocks
+
+	uint8_t csd[16]; // Unparsed CSD
+	uint8_t cid[16]; // Unparsed CID
+} SdDevice;
+
+extern SdDevice sdDev;
+
+int sdInit(void);
+
+void sdReadDMA(uint32_t lba, uint32_t sectors, uint8_t* outputBuffer);
+int sdReadDMAPoll(uint32_t remainingSectors);
+
+void sdReadCmd(uint32_t lba, uint32_t sectors);
+void sdReadPIOData(uint32_t sectors);
+
+void sdCompleteTransfer();
+void sdKeepAlive();
+
+int sdIsBusy();
+
+#endif

+ 178 - 0
lib/SCSI2SD/src/firmware/sense.h

@@ -0,0 +1,178 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef SENSE_H
+#define SENSE_H
+
+#include <stdint.h>
+
+typedef enum
+{
+	NO_SENSE                                               = 0,
+	RECOVERED_ERROR                                        = 1,
+	NOT_READY                                              = 2,
+	MEDIUM_ERROR                                           = 3,
+	HARDWARE_ERROR                                         = 4,
+	ILLEGAL_REQUEST                                        = 5,
+	UNIT_ATTENTION                                         = 6,
+	DATA_PROTECT                                           = 7,
+	BLANK_CHECK                                            = 8,
+	VENDOR_SPECIFIC                                        = 9,
+	COPY_ABORTED                                           = 0xA,
+	ABORTED_COMMAND                                        = 0xB,
+	EQUAL                                                  = 0xC,
+	VOLUME_OVERFLOW                                        = 0xD,
+	MISCOMPARE                                             = 0xE,
+	RESERVED                                               = 0xF
+} SCSI_SENSE;
+
+// Top 8 bits = ASC. Lower 8 bits = ASCQ.
+// Enum only contains definitions for direct-access related codes.
+typedef enum
+{
+	ADDRESS_MARK_NOT_FOUND_FOR_DATA_FIELD                  = 0x1300,
+	ADDRESS_MARK_NOT_FOUND_FOR_ID_FIELD                    = 0x1200,
+	CANNOT_READ_MEDIUM_INCOMPATIBLE_FORMAT                 = 0x3002,
+	CANNOT_READ_MEDIUM_UNKNOWN_FORMAT                      = 0x3001,
+	CHANGED_OPERATING_DEFINITION                           = 0x3F02,
+	COMMAND_PHASE_ERROR                                    = 0x4A00,
+	COMMAND_SEQUENCE_ERROR                                 = 0x2C00,
+	COMMANDS_CLEARED_BY_ANOTHER_INITIATOR                  = 0x2F00,
+	COPY_CANNOT_EXECUTE_SINCE_HOST_CANNOT_DISCONNECT       = 0x2B00,
+	DATA_PATH_FAILURE                                      = 0x4100,
+	DATA_PHASE_ERROR                                       = 0x4B00,
+	DATA_SYNCHRONIZATION_MARK_ERROR                        = 0x1600,
+	DEFECT_LIST_ERROR                                      = 0x1900,
+	DEFECT_LIST_ERROR_IN_GROWN_LIST                        = 0x1903,
+	DEFECT_LIST_ERROR_IN_PRIMARY_LIST                      = 0x1902,
+	DEFECT_LIST_NOT_AVAILABLE                              = 0x1901,
+	DEFECT_LIST_NOT_FOUND                                  = 0x1C00,
+	DEFECT_LIST_UPDATE_FAILURE                             = 0x3201,
+	ERROR_LOG_OVERFLOW                                     = 0x0A00,
+	ERROR_TOO_LONG_TO_CORRECT                              = 0x1102,
+	FORMAT_COMMAND_FAILED                                  = 0x3101,
+	GROWN_DEFECT_LIST_NOT_FOUND                            = 0x1C02,
+	IO_PROCESS_TERMINATED                                  = 0x0006,
+	ID_CRC_OR_ECC_ERROR                                    = 0x1000,
+	ILLEGAL_FUNCTION                                       = 0x2200,
+	INCOMPATIBLE_MEDIUM_INSTALLED                          = 0x3000,
+	INITIATOR_DETECTED_ERROR_MESSAGE_RECEIVED              = 0x4800,
+	INQUIRY_DATA_HAS_CHANGED                               = 0x3F03,
+	INTERNAL_TARGET_FAILURE                                = 0x4400,
+	INVALID_BITS_IN_IDENTIFY_MESSAGE                       = 0x3D00,
+	INVALID_COMMAND_OPERATION_CODE                         = 0x2000,
+	INVALID_FIELD_IN_CDB                                   = 0x2400,
+	INVALID_FIELD_IN_PARAMETER_LIST                        = 0x2600,
+	INVALID_MESSAGE_ERROR                                  = 0x4900,
+	LOG_COUNTER_AT_MAXIMUM                                 = 0x5B02,
+	LOG_EXCEPTION                                          = 0x5B00,
+	LOG_LIST_CODES_EXHAUSTED                               = 0x5B03,
+	LOG_PARAMETERS_CHANGED                                 = 0x2A02,
+	LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE                     = 0x2100,
+	LOGICAL_UNIT_COMMUNICATION_FAILURE                     = 0x0800,
+	LOGICAL_UNIT_COMMUNICATION_PARITY_ERROR                = 0x0802,
+	LOGICAL_UNIT_COMMUNICATION_TIMEOUT                     = 0x0801,
+	LOGICAL_UNIT_DOES_NOT_RESPOND_TO_SELECTION             = 0x0500,
+	LOGICAL_UNIT_FAILED_SELF_CONFIGURATION                 = 0x4C00,
+	LOGICAL_UNIT_HAS_NOT_SELF_CONFIGURED_YET               = 0x3E00,
+	LOGICAL_UNIT_IS_IN_PROCESS_OF_BECOMING_READY           = 0x0401,
+	LOGICAL_UNIT_NOT_READY_CAUSE_NOT_REPORTABLE            = 0x0400,
+	LOGICAL_UNIT_NOT_READY_FORMAT_IN_PROGRESS              = 0x0404,
+	LOGICAL_UNIT_NOT_READY_INITIALIZING_COMMAND_REQUIRED   = 0x0402,
+	LOGICAL_UNIT_NOT_READY_MANUAL_INTERVENTION_REQUIRED    = 0x0403,
+	LOGICAL_UNIT_NOT_SUPPORTED                             = 0x2500,
+	MECHANICAL_POSITIONING_ERROR                           = 0x1501,
+	MEDIA_LOAD_OR_EJECT_FAILED                             = 0x5300,
+	MEDIUM_FORMAT_CORRUPTED                                = 0x3100,
+	MEDIUM_NOT_PRESENT                                     = 0x3A00,
+	MEDIUM_REMOVAL_PREVENTED                               = 0x5302,
+	MESSAGE_ERROR                                          = 0x4300,
+	MICROCODE_HAS_BEEN_CHANGED                             = 0x3F01,
+	MISCOMPARE_DURING_VERIFY_OPERATION                     = 0x1D00,
+	MISCORRECTED_ERROR                                     = 0x110A,
+	MODE_PARAMETERS_CHANGED                                = 0x2A01,
+	MULTIPLE_PERIPHERAL_DEVICES_SELECTED                   = 0x0700,
+	MULTIPLE_READ_ERRORS                                   = 0x1103,
+	NO_ADDITIONAL_SENSE_INFORMATION                        = 0x0000,
+	NO_DEFECT_SPARE_LOCATION_AVAILABLE                     = 0x3200,
+	NO_INDEX_SECTOR_SIGNAL                                 = 0x0100,
+	NO_REFERENCE_POSITION_FOUND                            = 0x0600,
+	NO_SEEK_COMPLETE                                       = 0x0200,
+	NOT_READY_TO_READY_TRANSITION_MEDIUM_MAY_HAVE_CHANGED  = 0x2800,
+	OPERATOR_MEDIUM_REMOVAL_REQUEST                        = 0x5A01,
+	OPERATOR_REQUEST_OR_STATE_CHANGE_INPUT                 = 0x5A00,
+	OPERATOR_SELECTED_WRITE_PERMIT                         = 0x5A03,
+	OPERATOR_SELECTED_WRITE_PROTECT                        = 0x5A02,
+	OVERLAPPED_COMMANDS_ATTEMPTED                          = 0x4E00,
+	PARAMETER_LIST_LENGTH_ERROR                            = 0x1A00,
+	PARAMETER_NOT_SUPPORTED                                = 0x2601,
+	PARAMETER_VALUE_INVALID                                = 0x2602,
+	PARAMETERS_CHANGED                                     = 0x2A00,
+	PERIPHERAL_DEVICE_WRITE_FAULT                          = 0x0300,
+	POSITIONING_ERROR_DETECTED_BY_READ_OF_MEDIUM           = 0x1502,
+	POWER_ON_RESET_OR_BUS_DEVICE_RESET_OCCURRED            = 0x2900,
+	POWER_ON_RESET                                         = 0x2901,	
+	POWER_ON_OR_SELF_TEST_FAILURE                          = 0x4200,
+	PRIMARY_DEFECT_LIST_NOT_FOUND                          = 0x1C01,
+	RAM_FAILURE                                            = 0x4000,
+	RANDOM_POSITIONING_ERROR                               = 0x1500,
+	READ_RETRIES_EXHAUSTED                                 = 0x1101,
+	RECORD_NOT_FOUND                                       = 0x1401,
+	RECORDED_ENTITY_NOT_FOUND                              = 0x1400,
+	RECOVERED_DATA_DATA_AUTO_REALLOCATED                   = 0x1802,
+	RECOVERED_DATA_RECOMMEND_REASSIGNMENT                  = 0x1805,
+	RECOVERED_DATA_RECOMMEND_REWRITE                       = 0x1806,
+	RECOVERED_DATA_USING_PREVIOUS_SECTOR_ID                = 0x1705,
+	RECOVERED_DATA_WITH_ERROR_CORRECTION_RETRIES_APPLIED   = 0x1801,
+	RECOVERED_DATA_WITH_ERROR_CORRECTION_APPLIED           = 0x1800,
+	RECOVERED_DATA_WITH_NEGATIVE_HEAD_OFFSET               = 0x1703,
+	RECOVERED_DATA_WITH_NO_ERROR_CORRECTION_APPLIED        = 0x1700,
+	RECOVERED_DATA_WITH_POSITIVE_HEAD_OFFSET               = 0x1702,
+	RECOVERED_DATA_WITH_RETRIES                            = 0x1701,
+	RECOVERED_DATA_WITHOUT_ECC_DATA_AUTO_REALLOCATED       = 0x1706,
+	RECOVERED_DATA_WITHOUT_ECC_RECOMMEND_REASSIGNMENT      = 0x1707,
+	RECOVERED_DATA_WITHOUT_ECC_RECOMMEND_REWRITE           = 0x1708,
+	RECOVERED_ID_WITH_ECC_CORRECTION                       = 0x1E00,
+	ROUNDED_PARAMETER                                      = 0x3700,
+	RPL_STATUS_CHANGE                                      = 0x5C00,
+	SAVING_PARAMETERS_NOT_SUPPORTED                        = 0x3900,
+	SCSI_BUS_RESET                                         = 0x2902,
+	SCSI_PARITY_ERROR                                      = 0x4700,
+	SELECT_OR_RESELECT_FAILURE                             = 0x4500,
+	SPINDLES_NOT_SYNCHRONIZED                              = 0x5C02,
+	SPINDLES_SYNCHRONIZED                                  = 0x5C01,
+	SYNCHRONOUS_DATA_TRANSFER_ERROR                        = 0x1B00,
+	TARGET_OPERATING_CONDITIONS_HAVE_CHANGED               = 0x3F00,
+	THRESHOLD_CONDITION_MET                                = 0x5B01,
+	THRESHOLD_PARAMETERS_NOT_SUPPORTED                     = 0x2603,
+	TRACK_FOLLOWING_ERROR                                  = 0x0900,
+	UNRECOVERED_READ_ERROR                                 = 0x1100,
+	UNRECOVERED_READ_ERROR_AUTO_REALLOCATE_FAILED          = 0x1104,
+	UNRECOVERED_READ_ERROR_RECOMMEND_REASSIGNMENT          = 0x110B,
+	UNRECOVERED_READ_ERROR_RECOMMEND_REWRITE_THE_DATA      = 0x110C,
+	UNSUCCESSFUL_SOFT_RESET                                = 0x4600,
+	WRITE_ERROR_AUTO_REALLOCATION_FAILED                   = 0x0C02,
+	WRITE_ERROR_RECOVERED_WITH_AUTO_REALLOCATION           = 0x0C01,
+	WRITE_PROTECTED                                        = 0x2700
+} SCSI_ASC_ASCQ;
+
+typedef struct
+{
+	uint8_t code;
+	uint16_t asc;
+} ScsiSense;
+
+#endif

+ 29 - 0
lib/SCSI2SD/src/firmware/tape.c

@@ -0,0 +1,29 @@
+//	Copyright (C) 2015 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "config.h"
+#include "tape.h"
+
+// Handle sequential scsi device commands
+int scsiTapeCommand()
+{
+	// TODO handle tape-specific read/write commands and return 1
+
+	return 0;
+}
+

+ 22 - 0
lib/SCSI2SD/src/firmware/tape.h

@@ -0,0 +1,22 @@
+//	Copyright (C) 2015 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef TAPE_H
+#define TAPE_H
+
+int scsiTapeCommand(void);
+
+#endif

+ 58 - 0
lib/SCSI2SD/src/firmware/vendor.c

@@ -0,0 +1,58 @@
+//	Copyright (C) 2016 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "vendor.h"
+
+
+// Callback after the DATA OUT phase is complete.
+static void doAssignDiskParameters(void)
+{
+	scsiDev.phase = STATUS;
+}
+
+int scsiVendorCommand()
+{
+	int commandHandled = 1;
+
+	uint8_t command = scsiDev.cdb[0];
+
+	if (command == 0xC0)
+	{
+		// Define flexible disk format
+		// OMTI-5204 controller
+		// http://bitsavers.informatik.uni-stuttgart.de/pdf/sms/OMTI_5x00.pdf
+		// Stub. Sectors-per-track should be configured by scsi2sd-util
+	}
+	else if (command == 0xC2)
+	{
+		// Assign Disk Parameters command
+		// OMTI-5204 controller
+		// http://bitsavers.informatik.uni-stuttgart.de/pdf/sms/OMTI_5x00.pdf
+		// Stub to read and discard 10 bytes.
+		scsiDev.dataLen = 10;
+		scsiDev.phase = DATA_OUT;
+		scsiDev.postDataOutHook = doAssignDiskParameters;
+	}
+	else
+	{
+		commandHandled = 0;
+	}
+
+	return commandHandled;
+}
+

+ 22 - 0
lib/SCSI2SD/src/firmware/vendor.h

@@ -0,0 +1,22 @@
+//	Copyright (C) 2016 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that it will be useful,
+//	but WITHOUT ANY WARRANTY; without even the implied warranty of
+//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//	GNU General Public License for more details.
+//
+//	You should have received a copy of the GNU General Public License
+//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef S2S_VENDOR_H
+#define S2S_VENDOR_H
+
+int scsiVendorCommand(void);
+
+#endif

+ 22 - 0
lib/SdFat_NoArduino/.gitattributes

@@ -0,0 +1,22 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+# Custom for Visual Studio
+*.cs     diff=csharp
+*.sln    merge=union
+*.csproj merge=union
+*.vbproj merge=union
+*.fsproj merge=union
+*.dbproj merge=union
+
+# Standard to msysgit
+*.doc	 diff=astextplain
+*.DOC	 diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot  diff=astextplain
+*.DOT  diff=astextplain
+*.pdf  diff=astextplain
+*.PDF	 diff=astextplain
+*.rtf	 diff=astextplain
+*.RTF	 diff=astextplain

+ 215 - 0
lib/SdFat_NoArduino/.gitignore

@@ -0,0 +1,215 @@
+#################
+## Eclipse
+#################
+
+*.pydevproject
+.project
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.classpath
+.settings/
+.loadpath
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# CDT-specific
+.cproject
+
+# PDT-specific
+.buildpath
+
+
+#################
+## Visual Studio
+#################
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+
+[Dd]ebug/
+[Rr]elease/
+x64/
+build/
+[Bb]in/
+[Oo]bj/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+*_i.c
+*_p.c
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.log
+*.scc
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+*.ncrunch*
+.*crunch*.local.xml
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.Publish.xml
+*.pubxml
+
+# NuGet Packages Directory
+## TODO: If you have NuGet Package Restore enabled, uncomment the next line
+#packages/
+
+# Windows Azure Build Output
+csx
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+sql/
+*.Cache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.[Pp]ublish.xml
+*.pfx
+*.publishsettings
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file to a newer
+# Visual Studio version. Backup files are not needed, because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+App_Data/*.mdf
+App_Data/*.ldf
+
+#############
+## Windows detritus
+#############
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Mac crap
+.DS_Store
+
+
+#############
+## Python
+#############
+
+*.py[co]
+
+# Packages
+*.egg
+*.egg-info
+dist/
+build/
+eggs/
+parts/
+var/
+sdist/
+develop-eggs/
+.installed.cfg
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+
+#Translations
+*.mo
+
+#Mr Developer
+.mr.developer.cfg

+ 1 - 0
lib/SdFat_NoArduino/.piopm

@@ -0,0 +1 @@
+{"type": "library", "name": "SdFat", "version": "2.1.2", "spec": {"owner": "greiman", "id": 322, "name": "SdFat", "requirements": null, "url": null}}

+ 21 - 0
lib/SdFat_NoArduino/LICENSE.md

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2011..2020 Bill Greiman
+
+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.

+ 110 - 0
lib/SdFat_NoArduino/README.md

@@ -0,0 +1,110 @@
+### Warning: This is SdFat Version 2.
+
+Earlier releases of Version 1 are here:
+
+https://github.com/greiman/SdFat/releases
+
+UTF-8 encoded filenames are supported in v2.1.0 or later.
+
+Try the UnicodeFilenames example.  Here is output from ls:
+<pre>
+Type any character to begin
+ls:
+         0 😀/
+          20 россиянин
+          17 très élégant
+           9 狗.txt
+</pre>
+
+SdFat Version 2 supports FAT16/FAT32 and exFAT SD cards. It is mostly
+backward compatible with SdFat Version 1 for FAT16/FAT32 cards.
+
+exFAT supports files larger than 4GB so files sizes and positions are
+type uint64_t for classes that support exFAT.
+
+exFAT has many features not available in FAT16/FAT32.  exFAT has excellent
+support for contiguous files on flash devices and supports preallocation.
+
+If the SD card is the only SPI device, use dedicated SPI mode. This can
+greatly improve performance. See the bench example.
+
+Here is write performance for an old, 2011, card on a Due board.
+```
+Shared SPI:
+write speed and latency
+speed,max,min,avg
+KB/Sec,usec,usec,usec
+294.45,24944,1398,1737
+
+Dedicated SPI:
+write speed and latency
+speed,max,min,avg
+KB/Sec,usec,usec,usec
+3965.11,16733,110,127
+```
+The default version of SdFatConfig.h enables support for dedicated SPI and
+optimized access to contiguous files.  This makes SdFat Version 2 slightly
+larger than Version 1.  If these features are disabled, Version 2 is smaller
+than Version 1.
+
+The types for the classes SdFat and File are defined in SdFatConfig.h.
+The default version of SdFatConfig.h defines SdFat to only support FAT16/FAT32.
+SdFat and File are defined in terms of more basic classes by typedefs.  You
+can use these basic classes in applications.
+
+Support for exFAT requires a substantial amount of flash.  Here are sizes on
+an UNO for a simple program that opens a file, prints one line, and closes
+the file.
+```
+FAT16/FAT32 only: 9780 bytes flash, 875 bytes SRAM.
+
+exFAT only: 13830 bytes flash, 938 bytes SRAM.
+
+FAT16/FAT32/exFAT: 19326 bytes flash, 928 bytes SRAM.
+```
+The section below of SdFatConfig.h has been edited to uses FAT16/FAT32 for
+small AVR boards and FAT16/FAT32/exFAT for all other boards.
+```
+/**
+ * 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
+```
+The SdBaseFile class has no Arduino Stream or Print support.
+
+The File class is derived from Stream and SdBaseFile.
+
+The SdFile class is derived from SdBaseFile and Print.
+
+Please try the examples.  Start with SdInfo, bench, and ExFatLogger.
+
+To use SdFat Version 2, unzip the download file, rename the library folder
+SdFat and place the SdFat folder into the libraries sub-folder in your main
+sketch folder.
+
+For more information see the Manual installation section of this guide:
+
+http://arduino.cc/en/Guide/Libraries
+
+A number of configuration options can be set by editing SdFatConfig.h
+define macros.  See the html documentation File tab for details.
+
+Please read the html documentation for this library in SdFat/doc/SdFat.html.
+Start with the  Main Page.  Next go to the Classes tab and read the
+documentation for the classes SdFat32, SdExFat, SdFs, File32, ExFile, FsFile.
+
+The SdFat and File classes are defined in terms of the above classes by
+typedefs. Edit SdFatConfig.h to select class options.
+
+Please continue by reading the html documentation in the SdFat/doc folder.

+ 2685 - 0
lib/SdFat_NoArduino/doc/Doxyfile

@@ -0,0 +1,2685 @@
+# Doxyfile 1.9.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = SdFat
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          =
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = .
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = YES
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        = C:/Users/bill/Documents/
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER         = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# By default Python docstrings are displayed as preformatted text and doxygen's
+# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
+# doxygen's special commands can be used and the contents of the docstring
+# documentation blocks is shown as doxygen documentation.
+# The default value is: YES.
+
+PYTHON_DOCSTRING       = YES
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = NO
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:^^"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". Note that you cannot put \n's in the value part of an alias
+# to insert newlines (in the resulting output). You can put ^^ in the value part
+# of an alias to insert a newline as if a physical newline was in the original
+# file. When you need a literal { or } or , in the value part of an alias you
+# have to escape them by means of a backslash (\), this can lead to conflicts
+# with the commands \{ and \} for these it is advised to use the version @{ and
+# @} or use a double escape (\\{ and \\})
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE  = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files). For instance to make doxygen treat .inc files
+# as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen. When specifying no_extension you should add
+# * to the FILE_PATTERNS.
+#
+# Note see also the list of default file extension mappings.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 5.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS   = 0
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
+# during processing. When set to 0 doxygen will based this on the number of
+# cores available in the system. You can set it explicitly to a value larger
+# than 0 to get more control over the balance between CPU load and processing
+# speed. At this moment only the input processing can be done using multiple
+# threads. Since this is still an experimental feature the default is set to 1,
+# which effectively disables parallel processing. Please report any issues you
+# encounter. Generating dot graphs in parallel is controlled by the
+# DOT_NUM_THREADS setting.
+# Minimum value: 0, maximum value: 32, default value: 1.
+
+NUM_PROC_THREADS       = 1
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL   = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = NO
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If this flag is set to YES, the name of an unnamed parameter in a declaration
+# will be determined by the corresponding definition. By default unnamed
+# parameters remain unnamed in the output.
+# The default value is: YES.
+
+RESOLVE_UNNAMED_PARAMS = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
+# able to match the capabilities of the underlying filesystem. In case the
+# filesystem is case sensitive (i.e. it supports files in the same directory
+# whose names only differ in casing), the option must be set to YES to properly
+# deal with such files in case they appear in the input. For filesystems that
+# are not case sensitive the option should be be set to NO to properly deal with
+# output files written for symbols that only differ in casing, such as for two
+# classes, one named CLASS and the other named Class, and to also support
+# references to files without having to specify the exact matching casing. On
+# Windows (including Cygwin) and MacOS, users should typically set this option
+# to NO, whereas on Linux or other Unix flavors it should typically be set to
+# YES.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
+# will show which file needs to be included to use the class.
+# The default value is: YES.
+
+SHOW_HEADERFILE        = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = YES
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 2
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file. See also section "Changing the
+# layout of pages" for information.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as documenting some parameters in
+# a documented function twice, or documenting parameters that don't exist or
+# using markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
+# function parameter documentation. If set to NO, doxygen will accept that some
+# parameters have no documentation without warning.
+# The default value is: YES.
+
+WARN_IF_INCOMPLETE_DOC = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong parameter
+# documentation, but not about the absence of documentation. If EXTRACT_ALL is
+# set to YES then this flag will automatically be disabled. See also
+# WARN_IF_INCOMPLETE_DOC
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = YES
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
+# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
+# at the end of the doxygen process doxygen will return with a non-zero status.
+# Possible values are: NO, YES and FAIL_ON_WARNINGS.
+# The default value is: NO.
+
+WARN_AS_ERROR          = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = ../src \
+                         ../src/ExFatLib \
+                         ../src/FatLib \
+                         ../src/iostream \
+                         ../src/common \
+                         ../src/SdCard \
+                         ../src/SpiDriver \
+                         mainpage.h \
+                         ../src/FsLib \
+                         ../src/FsLib
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see:
+# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# Note the list of default checked file patterns might differ from the list of
+# default file extension mappings.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
+# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
+# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
+# *.vhdl, *.ucf, *.qsf and *.ice.
+
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.c++ \
+                         *.d \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.idl \
+                         *.odl \
+                         *.cs \
+                         *.php \
+                         *.php3 \
+                         *.inc \
+                         *.m \
+                         *.mm \
+                         *.dox \
+                         *.py \
+                         *.f90 \
+                         *.f \
+                         *.vhd \
+                         *.vhdl \
+                         *.cpp
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                = ../src/common/FsStructs.h \
+                         ../src/ExFatLib/upcase.cpp \
+                         ../src/common/PrintBasic.h \
+                         ../src/common/PrintBasic.cpp \
+                         ../src/SpiDriver/SdSpiBareUnoDriver.h \
+                         ../src/iostream/StreamBaseClass.cpp
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = NO
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see:
+# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
+# performance. This can be particularly helpful with template rich C++ code for
+# which doxygen's built-in parser lacks the necessary type information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
+# tag is set to YES then doxygen will add the directory of each input to the
+# include path.
+# The default value is: YES.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_ADD_INC_PATHS    = YES
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS          =
+
+# If clang assisted parsing is enabled you can provide the clang parser with the
+# path to the directory containing a file called compile_commands.json. This
+# file is the compilation database (see:
+# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
+# options used when the source files were built. This is equivalent to
+# specifying the -p option to a clang tool, such as clang-check. These options
+# will then be passed to the parser. Any options specified with CLANG_OPTIONS
+# will be added as well.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+
+CLANG_DATABASE_PATH    =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = NO
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a color-wheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use gray-scales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via JavaScript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have JavaScript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see:
+# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
+# create a documentation set, doxygen will generate a Makefile in the HTML
+# output directory. Running make will produce the docset in that directory and
+# running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# on Windows. In the beginning of 2021 Microsoft took the original page, with
+# a.o. the download links, offline the HTML help workshop was already many years
+# in maintenance mode). You can download the HTML help workshop from the web
+# archives at Installation executable (see:
+# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
+# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the main .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location (absolute path
+# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
+# run qhelpgenerator on the generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine tune the look of the index (see "Fine-tuning the output"). As an
+# example, the default style sheet generated by doxygen has an example that
+# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
+# Since the tree basically has the same information as the tab index, you could
+# consider setting DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = NO
+
+# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
+# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
+# area (value NO) or if it should extend to the full height of the window (value
+# YES). Setting this to YES gives a layout similar to
+# https://docs.readthedocs.io with more room for contents, but less room for the
+# project logo, title, and description. If either GENERATOR_TREEVIEW or
+# DISABLE_INDEX is set to NO, this option has no effect.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FULL_SIDEBAR           = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
+# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
+# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
+# the HTML output. These images will generally look nicer at scaled resolutions.
+# Possible values are: png (the default) and svg (looks nicer but requires the
+# pdf2svg or inkscape tool).
+# The default value is: png.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FORMULA_FORMAT    = png
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE      =
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
+# Note that the different versions of MathJax have different requirements with
+# regards to the different settings, so it is possible that also other MathJax
+# settings have to be changed when switching between the different MathJax
+# versions.
+# Possible values are: MathJax_2 and MathJax_3.
+# The default value is: MathJax_2.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_VERSION        = MathJax_2
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. For more details about the output format see MathJax
+# version 2 (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
+# (see:
+# http://docs.mathjax.org/en/latest/web/components/output.html).
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility. This is the name for Mathjax version 2, for MathJax version 3
+# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
+# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
+# is the name for Mathjax version 3, for MathJax version 2 this will be
+# translated into HTML-CSS) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment. The default value is:
+# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
+# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# for MathJax version 2 (see
+# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# For example for MathJax version 3 (see
+# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
+# MATHJAX_EXTENSIONS = ams
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using JavaScript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/). See the section "External Indexing and Searching" for
+# details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD    = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
+# the generated LaTeX document. The header should contain everything until the
+# first chapter. If it is left blank doxygen will generate a standard header. It
+# is highly recommended to start with a default header using
+# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
+# and then modify the file new_header.tex. See also section "Doxygen usage" for
+# information on how to generate the default header that doxygen normally uses.
+#
+# Note: Only use a user-defined header if you know what you are doing!
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. The following
+# commands have a special meaning inside the header (and footer): For a
+# description of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
+# the generated LaTeX document. The footer should contain everything after the
+# last chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer. See also section "Doxygen
+# usage" for information on how to generate the default footer that doxygen
+# normally uses. Note: Only use a user-defined footer if you know what you are
+# doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
+# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
+# files. Set this option to YES, to get a higher quality PDF documentation.
+#
+# See also section LATEX_CMD_NAME for selecting the engine.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = YES
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    = rtfstyle.cfg
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to Sqlite3 output
+#---------------------------------------------------------------------------
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             = __attribute__(x)= \
+                         DOXYGEN \
+                         protected=private \
+                         DOXYGEN_SHOULD_SKIP_THIS \
+                         ARDUINO
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT               = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
+# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
+# tag is set to YES, doxygen will add type and arguments for attributes and
+# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
+# will not generate fields with class member information in the UML graphs. The
+# class diagrams will look similar to the default class diagrams but using UML
+# notation for the relationships.
+# Possible values are: NO, YES and NONE.
+# The default value is: NO.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+DOT_UML_DETAILS        = NO
+
+# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
+# to display on a single line. If the actual line length exceeds this threshold
+# significantly it will wrapped across multiple lines. Some heuristics are apply
+# to avoid ugly line breaks.
+# Minimum value: 0, maximum value: 1000, default value: 17.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_WRAP_THRESHOLD     = 17
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 1000
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = YES
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
+# files that are used to generate the various graphs.
+#
+# Note: This setting is not only used for dot files but also for msc temporary
+# files.
+# The default value is: YES.
+
+DOT_CLEANUP            = YES

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

@@ -0,0 +1,50 @@
+2021-01-06
+
+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_READ_TOKEN - Bad read data token
+0X19,SD_CARD_ERROR_READ_CRC - Read CRC error
+0X1A,SD_CARD_ERROR_READ_FIFO - SDIO fifo read timeout
+0X1B,SD_CARD_ERROR_READ_REG - Read CID or CSD failed.
+0X1C,SD_CARD_ERROR_READ_START - Bad readStart argument
+0X1D,SD_CARD_ERROR_READ_TIMEOUT - Read data timeout
+0X1E,SD_CARD_ERROR_STOP_TRAN - Multiple block stop failed
+0X1F,SD_CARD_ERROR_TRANSFER_COMPLETE - SDIO transfer complete
+0X20,SD_CARD_ERROR_WRITE_DATA - Write data not accepted
+0X21,SD_CARD_ERROR_WRITE_FIFO - SDIO fifo write timeout
+0X22,SD_CARD_ERROR_WRITE_START - Bad writeStart argument
+0X23,SD_CARD_ERROR_WRITE_PROGRAMMING - Flash programming
+0X24,SD_CARD_ERROR_WRITE_TIMEOUT - Write timeout
+0X25,SD_CARD_ERROR_DMA - DMA transfer failed
+0X26,SD_CARD_ERROR_ERASE - Card did not accept erase commands
+0X27,SD_CARD_ERROR_ERASE_SINGLE_SECTOR - Card does not support erase
+0X28,SD_CARD_ERROR_ERASE_TIMEOUT - Erase command timeout
+0X29,SD_CARD_ERROR_INIT_NOT_CALLED - Card has not been initialized
+0X2A,SD_CARD_ERROR_INVALID_CARD_CONFIG - Invalid card config
+0X2B,SD_CARD_ERROR_FUNCTION_NOT_SUPPORTED - Unsupported SDIO command
+0X2C,SD_CARD_ERROR_UNKNOWN - Unknown error

+ 10 - 0
lib/SdFat_NoArduino/doc/SdFat.html

@@ -0,0 +1,10 @@
+<html>
+<head>
+<title>A web page that points a browser to a different page</title>
+<meta http-equiv="refresh" content="0; URL=html/index.html">
+<meta name="keywords" content="automatic redirection">
+</head>
+<body>
+Your browser didn't automatically redirect. Open html/index.html manually.
+</body>
+</html>

+ 4 - 0
lib/SdFat_NoArduino/doc/ZipMsg/index.html

@@ -0,0 +1,4 @@
+<h3>Replace the content of the html folder by unzipping html.zip.</h3>
+<h3>I have zipped the documentation since Doxygen changes every file each time it runs.</h3>
+<h3>This makes viewing changes on GitHub difficult.</h3>
+<p>&nbsp;</p>

+ 3 - 0
lib/SdFat_NoArduino/doc/clean_html.bat

@@ -0,0 +1,3 @@
+del html\*.md5
+del html\*.map
+pause

+ 3 - 0
lib/SdFat_NoArduino/doc/del_html.bat

@@ -0,0 +1,3 @@
+rm html/*.*
+rm html/search/*.*
+pause

BIN
lib/SdFat_NoArduino/doc/html.zip


+ 4 - 0
lib/SdFat_NoArduino/doc/html/index.html

@@ -0,0 +1,4 @@
+<h3>Replace the content of the html folder by unzipping html.zip.</h3>
+<h3>I have zipped the documentation since Doxygen changes every file each time it runs.</h3>
+<h3>This makes viewing changes on GitHub difficult.</h3>
+<p>&nbsp;</p>

+ 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.
+ */

+ 31 - 0
lib/SdFat_NoArduino/examples/AvrAdcLogger/AvrAdcLogger.h

@@ -0,0 +1,31 @@
+#ifndef AnalogBinLogger_h
+#define AnalogBinLogger_h
+const size_t BLOCK_SIZE = 64;
+//------------------------------------------------------------------------------
+// First block of file.
+const size_t PIN_NUM_DIM = BLOCK_SIZE - 3*sizeof(uint32_t) - 2*sizeof(uint8_t);
+struct metadata_t {
+  uint32_t adcFrequency;           // ADC clock frequency
+  uint32_t cpuFrequency;           // CPU clock frequency
+  uint32_t sampleInterval;         // Sample interval in CPU cycles.
+  uint8_t recordEightBits;         // Size of ADC values, nonzero for 8-bits.
+  uint8_t pinCount;                // Number of analog pins in a sample.
+  uint8_t pinNumber[PIN_NUM_DIM];  // List of pin numbers in a sample.
+};
+//------------------------------------------------------------------------------
+// Data block for 8-bit ADC mode.
+const size_t DATA_DIM8 = (BLOCK_SIZE - 2*sizeof(uint16_t))/sizeof(uint8_t);
+struct block8_t {
+  uint16_t count;    // count of data values
+  uint16_t overrun;  // count of overruns since last block
+  uint8_t  data[DATA_DIM8];
+};
+//------------------------------------------------------------------------------
+// Data block for 10-bit ADC mode.
+const size_t DATA_DIM16 = (BLOCK_SIZE - 2*sizeof(uint16_t))/sizeof(uint16_t);
+struct block16_t {
+  unsigned short count;    // count of data values
+  unsigned short overrun;  // count of overruns since last block
+  unsigned short data[DATA_DIM16];
+};
+#endif  // AnalogBinLogger_h

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

@@ -0,0 +1,898 @@
+/**
+ * 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__

+ 83 - 0
lib/SdFat_NoArduino/examples/BackwardCompatibility/BackwardCompatibility.ino

@@ -0,0 +1,83 @@
+// A simple read/write example for SD.h.
+// Mostly from the SD.h ReadWrite example.
+//
+// Your SD must be formatted FAT16/FAT32.
+//
+// SD.h does not support some default SdFat features.
+// To compare flash size, set USE_FAT_FILE_FLAG_CONTIGUOUS,
+// ENABLE_DEDICATED_SPI, and USE_LONG_FILE_NAMES to zero also
+// set SDFAT_FILE_TYPE to one in SdFat/src/SdFatCongfig.h
+//
+// Set USE_SD_H nonzero to use SD.h.
+// Set USE_SD_H zero to use SdFat.h.
+//
+#define USE_SD_H 0
+//
+#if USE_SD_H
+#include <SD.h>
+#else  // USE_SD_H
+#include "SdFat.h"
+SdFat SD;
+#endif  // USE_SD_H
+
+// Modify SD_CS_PIN for your board.
+// For Teensy 3.6 and SdFat.h use BUILTIN_SDCARD.
+#define SD_CS_PIN SS
+
+File myFile;
+
+void setup() {
+  Serial.begin(9600);
+  while (!Serial) {}
+
+#if USE_SD_H
+  Serial.println(F("Using SD.h. Set USE_SD_H zero to use SdFat.h."));
+#else  // USE_SD_H
+  Serial.println(F("Using SdFat.h. Set USE_SD_H nonzero to use SD.h."));
+#endif  // USE_SD_H
+  Serial.println(F("\nType any character to begin."));
+  while (!Serial.available()) {
+    yield();
+  }
+  Serial.print("Initializing SD card...");
+
+  if (!SD.begin(SD_CS_PIN)) {
+    Serial.println("initialization failed!");
+    return;
+  }
+  Serial.println("initialization done.");
+
+  // open the file.
+  myFile = SD.open("test.txt", FILE_WRITE);
+
+  // if the file opened okay, write to it:
+  if (myFile) {
+    Serial.print("Writing to test.txt...");
+    myFile.println("testing 1, 2, 3.");
+    // close the file:
+    myFile.close();
+    Serial.println("done.");
+  } else {
+    // if the file didn't open, print an error:
+    Serial.println("error opening test.txt");
+  }
+
+  // re-open the file for reading:
+  myFile = SD.open("test.txt");
+  if (myFile) {
+    Serial.println("test.txt:");
+
+    // read from the file until there's nothing else in it:
+    while (myFile.available()) {
+      Serial.write(myFile.read());
+    }
+    // close the file:
+    myFile.close();
+  } else {
+    // if the file didn't open, print an error:
+    Serial.println("error opening test.txt");
+  }
+}
+void loop() {
+  // nothing happens after setup
+}

+ 235 - 0
lib/SdFat_NoArduino/examples/BufferedPrint/BufferedPrint.ino

@@ -0,0 +1,235 @@
+// Test and benchmark of the fast bufferedPrint class.
+//
+// Mainly for AVR but may improve print performance with other CPUs.
+#include "SdFat.h"
+#include "BufferedPrint.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
+
+#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
+
+// number of lines to print
+const uint16_t N_PRINT = 20000;
+//------------------------------------------------------------------------------
+void benchmark() {
+  file_t file;
+  BufferedPrint<file_t, 64> bp;
+  // do write test
+  Serial.println();
+  for (int test = 0; test < 6; test++) {
+    char fileName[13] = "bench0.txt";
+    fileName[5] = '0' + test;
+    // open or create file - truncate existing file.
+    if (!file.open(fileName, O_RDWR | O_CREAT | O_TRUNC)) {
+      sd.errorHalt(&Serial, F("open failed"));
+    }
+    if (test & 1) {
+      bp.begin(&file);
+    }
+    uint32_t t = millis();
+    switch(test) {
+    case 0:
+      Serial.println(F("Test of println(uint16_t)"));
+      for (uint16_t i = 0; i < N_PRINT; i++) {
+        file.println(i);
+      }
+      break;
+
+    case 1:
+      Serial.println(F("Test of printField(uint16_t, char)"));
+      for (uint16_t i = 0; i < N_PRINT; i++) {
+        bp.printField(i, '\n');
+      }
+      break;
+
+    case 2:
+      Serial.println(F("Test of println(uint32_t)"));
+      for (uint16_t i = 0; i < N_PRINT; i++) {
+        file.println(12345678UL + i);
+      }
+      break;
+
+    case 3:
+      Serial.println(F("Test of printField(uint32_t, char)"));
+      for (uint16_t i = 0; i < N_PRINT; i++) {
+        bp.printField(12345678UL + i, '\n');
+      }
+      break;
+
+    case 4:
+      Serial.println(F("Test of println(double)"));
+      for (uint16_t i = 0; i < N_PRINT; i++) {
+        file.println((double)0.01*i);
+      }
+      break;
+
+    case 5:
+      Serial.println(F("Test of printField(double, char)"));
+      for (uint16_t i = 0; i < N_PRINT; i++) {
+        bp.printField((double)0.01*i, '\n');
+      }
+      break;
+
+    }
+    if (test & 1) {
+      bp.sync();
+    }
+    if (file.getWriteError()) {
+      sd.errorHalt(&Serial, F("write failed"));
+    }
+    double s = file.fileSize();
+    file.close();
+    t = millis() - t;
+    Serial.print(F("Time "));
+    Serial.print(0.001*t, 3);
+    Serial.println(F(" sec"));
+    Serial.print(F("File size "));
+    Serial.print(0.001*s);
+    Serial.println(F(" KB"));
+    Serial.print(F("Write "));
+    Serial.print(s/t);
+    Serial.println(F(" KB/sec"));
+    Serial.println();
+  }
+}
+//------------------------------------------------------------------------------
+void testMemberFunctions() {
+  BufferedPrint<Print, 32> bp(&Serial);
+  char c = 'c';    // char
+//#define BASIC_TYPES
+#ifdef BASIC_TYPES
+  signed char sc = -1;   // signed 8-bit
+  unsigned char uc = 1;  // unsiged 8-bit
+  signed short ss = -2;  // signed 16-bit
+  unsigned short us = 2; // unsigned 16-bit
+  signed long sl = -4;   // signed 32-bit
+  unsigned long ul = 4;  // unsigned 32-bit
+#else  // BASIC_TYPES
+  int8_t sc = -1;  // signed 8-bit
+  uint8_t uc = 1;  // unsiged 8-bit
+  int16_t ss = -2; // signed 16-bit
+  uint16_t us = 2; // unsigned 16-bit
+  int32_t sl = -4; // signed 32-bit
+  uint32_t ul = 4; // unsigned 32-bit
+#endif  // BASIC_TYPES
+  float f = -1.234;
+  double d = -5.678;
+  bp.println();
+  bp.println("Test print()");
+  bp.print(c);
+  bp.println();
+  bp.print("string");
+  bp.println();
+  bp.print(F("flash"));
+  bp.println();
+  bp.print(sc);
+  bp.println();
+  bp.print(uc);
+  bp.println();
+  bp.print(ss);
+  bp.println();
+  bp.print(us);
+  bp.println();
+  bp.print(sl);
+  bp.println();
+  bp.print(ul);
+  bp.println();
+  bp.print(f);
+  bp.println();
+  bp.print(d);
+  bp.println();
+  bp.println();
+
+  bp.println("Test println()");
+  bp.println(c);
+  bp.println("string");
+  bp.println(F("flash"));
+  bp.println(sc);
+  bp.println(uc);
+  bp.println(ss);
+  bp.println(us);
+  bp.println(sl);
+  bp.println(ul);
+  bp.println(f);
+  bp.println(d);
+  bp.println();
+
+  bp.println("Test printField()");
+  bp.printField(c, ',');
+  bp.printField("string", ',');
+  bp.printField(F("flash"), ',');
+  bp.printField(sc, ',');
+  bp.printField(uc, ',');
+  bp.printField(ss, ',');
+  bp.printField(us, ',');
+  bp.printField(sl, ',');
+  bp.printField(ul, ',');
+  bp.printField(f, ',');
+  bp.printField(d, '\n');
+
+  bp.sync();
+}
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+  while (!Serial) {}
+  Serial.println("Type any character to begin.");
+  while(!Serial.available()) {}
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+  }
+  Serial.println();
+  Serial.println(F("Test member funcions:"));
+  testMemberFunctions();
+  Serial.println();
+  Serial.println(F("Benchmark performance for uint16_t, uint32_t, and double:"));
+  benchmark();
+  Serial.println("Done");
+}
+//------------------------------------------------------------------------------
+void loop() {
+}

+ 158 - 0
lib/SdFat_NoArduino/examples/DirectoryFunctions/DirectoryFunctions.ino

@@ -0,0 +1,158 @@
+/*
+ * Example use of chdir(), ls(), mkdir(), and  rmdir().
+ */
+#include "SdFat.h"
+#include "sdios.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
+//------------------------------------------------------------------------------
+
+#if SD_FAT_TYPE == 0
+SdFat sd;
+File file;
+File root;
+#elif SD_FAT_TYPE == 1
+SdFat32 sd;
+File32 file;
+File32 root;
+#elif SD_FAT_TYPE == 2
+SdExFat sd;
+ExFile file;
+ExFile root;
+#elif SD_FAT_TYPE == 3
+SdFs sd;
+FsFile file;
+FsFile root;
+#endif  // SD_FAT_TYPE
+
+// Create a Serial output stream.
+ArduinoOutStream cout(Serial);
+//------------------------------------------------------------------------------
+// Store error strings in flash to save RAM.
+#define error(s) sd.errorHalt(&Serial, F(s))
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  delay(1000);
+  cout << F("Type any character to start\n");
+  while (!Serial.available()) {
+    yield();
+  }
+
+  // Initialize the SD card.
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+  }
+  if (sd.exists("Folder1")
+    || sd.exists("Folder1/file1.txt")
+    || sd.exists("Folder1/File2.txt")) {
+    error("Please remove existing Folder1, file1.txt, and File2.txt");
+  }
+
+  int rootFileCount = 0;
+  if (!root.open("/")) {
+    error("open root");
+  }
+  while (file.openNext(&root, O_RDONLY)) {
+    if (!file.isHidden()) {
+      rootFileCount++;
+    }
+    file.close();
+    if (rootFileCount > 10) {
+      error("Too many files in root. Please use an empty SD.");
+    }
+  }
+  if (rootFileCount) {
+    cout << F("\nPlease use an empty SD for best results.\n\n");
+    delay(1000);
+  }
+  // Create a new folder.
+  if (!sd.mkdir("Folder1")) {
+    error("Create Folder1 failed");
+  }
+  cout << F("Created Folder1\n");
+
+  // Create a file in Folder1 using a path.
+  if (!file.open("Folder1/file1.txt", O_WRONLY | O_CREAT)) {
+    error("create Folder1/file1.txt failed");
+  }
+  file.close();
+  cout << F("Created Folder1/file1.txt\n");
+
+  // Change volume working directory to Folder1.
+  if (!sd.chdir("Folder1")) {
+    error("chdir failed for Folder1.\n");
+  }
+  cout << F("chdir to Folder1\n");
+
+  // Create File2.txt in current directory.
+  if (!file.open("File2.txt", O_WRONLY | O_CREAT)) {
+    error("create File2.txt failed");
+  }
+  file.close();
+  cout << F("Created File2.txt in current directory\n");
+
+  cout << F("\nList of files on the SD.\n");
+  sd.ls("/", LS_R);
+
+  // Remove files from current directory.
+  if (!sd.remove("file1.txt") || !sd.remove("File2.txt")) {
+    error("remove failed");
+  }
+  cout << F("\nfile1.txt and File2.txt removed.\n");
+
+  // Change current directory to root.
+  if (!sd.chdir()) {
+    error("chdir to root failed.\n");
+  }
+
+  cout << F("\nList of files on the SD.\n");
+  sd.ls(LS_R);
+
+  // Remove Folder1.
+  if (!sd.rmdir("Folder1")) {
+    error("rmdir for Folder1 failed\n");
+  }
+  cout << F("\nFolder1 removed.\n");
+  cout << F("\nList of files on the SD.\n");
+  sd.ls(LS_R);
+  cout << F("Done!\n");
+}
+//------------------------------------------------------------------------------
+// Nothing happens in loop.
+void loop() {}

+ 9 - 0
lib/SdFat_NoArduino/examples/ExFatLogger/ExFatLogger.h

@@ -0,0 +1,9 @@
+// Avoid IDE problems by defining struct in septate .h file.
+// Pad record so size is a power of two for best write performance.
+#ifndef ExFatLogger_h
+#define ExFatLogger_h
+const size_t ADC_COUNT = 4;
+struct data_t {
+  uint16_t adc[ADC_COUNT];
+};
+#endif  // ExFatLogger_h

+ 595 - 0
lib/SdFat_NoArduino/examples/ExFatLogger/ExFatLogger.ino

@@ -0,0 +1,595 @@
+// Example to demonstrate write latency for preallocated exFAT files.
+// I suggest you write a PC program to convert very large bin files.
+//
+// The maximum data rate will depend on the quality of your SD,
+// the size of the FIFO, and using dedicated SPI.
+#include "SdFat.h"
+#include "FreeStack.h"
+#include "ExFatLogger.h"
+//------------------------------------------------------------------------------
+// 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
+//------------------------------------------------------------------------------
+// Interval between data records in microseconds.
+// Try 250 with Teensy 3.6, Due, or STM32.
+// Try 2000 with AVR boards.
+// Try 4000 with SAMD Zero boards.
+const uint32_t LOG_INTERVAL_USEC = 2000;
+
+// 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
+
+// LED to light if overruns occur.
+#define ERROR_LED_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
+// Assume built-in SD is used.
+const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
+#endif  // SDCARD_SS_PIN
+
+// FIFO SIZE - 512 byte sectors.  Modify for your board.
+#ifdef __AVR_ATmega328P__
+// Use 512 bytes for 328 boards.
+#define FIFO_SIZE_SECTORS 1
+#elif defined(__AVR__)
+// Use 2 KiB for other AVR boards.
+#define FIFO_SIZE_SECTORS 4
+#else  // __AVR_ATmega328P__
+// Use 8 KiB for non-AVR boards.
+#define FIFO_SIZE_SECTORS 16
+#endif  // __AVR_ATmega328P__
+
+// Preallocate 1GiB file.
+const uint32_t PREALLOCATE_SIZE_MiB = 1024UL;
+
+// 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
+
+// Save SRAM if 328.
+#ifdef __AVR_ATmega328P__
+#include "MinimumSerial.h"
+MinimumSerial MinSerial;
+#define Serial MinSerial
+#endif  // __AVR_ATmega328P__
+//==============================================================================
+// Replace logRecord(), printRecord(), and ExFatLogger.h for your sensors.
+void logRecord(data_t* data, uint16_t overrun) {
+  if (overrun) {
+    // Add one since this record has no adc data. Could add overrun field.
+    overrun++;
+    data->adc[0] = 0X8000 | overrun;
+  } else {
+    for (size_t i = 0; i < ADC_COUNT; i++) {
+      data->adc[i] = analogRead(i);
+    }
+  }
+}
+//------------------------------------------------------------------------------
+void printRecord(Print* pr, data_t* data) {
+  static uint32_t nr = 0;
+  if (!data) {
+    pr->print(F("LOG_INTERVAL_USEC,"));
+    pr->println(LOG_INTERVAL_USEC);
+    pr->print(F("rec#"));
+    for (size_t i = 0; i < ADC_COUNT; i++) {
+      pr->print(F(",adc"));
+      pr->print(i);
+    }
+    pr->println();
+    nr = 0;
+    return;
+  }
+  if (data->adc[0] & 0X8000) {
+    uint16_t n = data->adc[0] & 0X7FFF;
+    nr += n;
+    pr->print(F("-1,"));
+    pr->print(n);
+    pr->println(F(",overuns"));
+  } else {
+    pr->print(nr++);
+    for (size_t i = 0; i < ADC_COUNT; i++) {
+      pr->write(',');
+      pr->print(data->adc[i]);
+    }
+    pr->println();
+  }
+}
+//==============================================================================
+const uint64_t PREALLOCATE_SIZE  =  (uint64_t)PREALLOCATE_SIZE_MiB << 20;
+// Max length of file name including zero byte.
+#define FILE_NAME_DIM 40
+// Max number of records to buffer while SD is busy.
+const size_t FIFO_DIM = 512*FIFO_SIZE_SECTORS/sizeof(data_t);
+
+#if SD_FAT_TYPE == 0
+typedef SdFat sd_t;
+typedef File file_t;
+#elif SD_FAT_TYPE == 1
+typedef SdFat32 sd_t;
+typedef File32 file_t;
+#elif SD_FAT_TYPE == 2
+typedef SdExFat sd_t;
+typedef ExFile file_t;
+#elif SD_FAT_TYPE == 3
+typedef SdFs sd_t;
+typedef FsFile file_t;
+#else  // SD_FAT_TYPE
+#error Invalid SD_FAT_TYPE
+#endif  // SD_FAT_TYPE
+
+sd_t sd;
+
+file_t binFile;
+file_t csvFile;
+// You may modify the filename.  Digits before the dot are file versions.
+char binName[] = "ExFatLogger00.bin";
+//------------------------------------------------------------------------------
+#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
+//------------------------------------------------------------------------------
+#define error(s) sd.errorHalt(&Serial, F(s))
+#define dbgAssert(e) ((e) ? (void)0 : error("assert " #e))
+//-----------------------------------------------------------------------------
+// Convert binary file to csv file.
+void binaryToCsv() {
+  uint8_t lastPct = 0;
+  uint32_t t0 = millis();
+  data_t binData[FIFO_DIM];
+
+  if (!binFile.seekSet(512)) {
+	  error("binFile.seek failed");
+  }
+  uint32_t tPct = millis();
+  printRecord(&csvFile, nullptr);
+  while (!Serial.available() && binFile.available()) {
+    int nb = binFile.read(binData, sizeof(binData));
+    if (nb <= 0 ) {
+      error("read binFile failed");
+    }
+    size_t nr = nb/sizeof(data_t);
+    for (size_t i = 0; i < nr; i++) {
+      printRecord(&csvFile, &binData[i]);
+    }
+
+    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('%');
+        csvFile.sync();
+      }
+    }
+    if (Serial.available()) {
+      break;
+    }
+  }
+  csvFile.close();
+  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';
+    }
+  }
+  if (!binFile.open(binName, O_RDWR | O_CREAT)) {
+    error("open binName failed");
+  }
+  Serial.println(binName);
+  if (!binFile.preAllocate(PREALLOCATE_SIZE)) {
+    error("preAllocate failed");
+  }
+
+  Serial.print(F("preAllocated: "));
+  Serial.print(PREALLOCATE_SIZE_MiB);
+  Serial.println(F(" MiB"));
+}
+//-------------------------------------------------------------------------------
+bool createCsvFile() {
+  char csvName[FILE_NAME_DIM];
+  if (!binFile.isOpen()) {
+    Serial.println(F("No current binary file"));
+    return false;
+  }
+
+  // Create a new csvFile.
+  binFile.getName(csvName, sizeof(csvName));
+  char* dot = strchr(csvName, '.');
+  if (!dot) {
+    error("no dot in filename");
+  }
+  strcpy(dot + 1, "csv");
+  if (!csvFile.open(csvName, O_WRONLY | O_CREAT | O_TRUNC)) {
+    error("open csvFile failed");
+  }
+  clearSerialInput();
+  Serial.print(F("Writing: "));
+  Serial.print(csvName);
+  Serial.println(F(" - type any character to stop"));
+  return true;
+}
+//-------------------------------------------------------------------------------
+void logData() {
+  int32_t delta;  // Jitter in log time.
+  int32_t maxDelta = 0;
+  uint32_t maxLogMicros = 0;
+  uint32_t maxWriteMicros = 0;
+  size_t maxFifoUse = 0;
+  size_t fifoCount = 0;
+  size_t fifoHead = 0;
+  size_t fifoTail = 0;
+  uint16_t overrun = 0;
+  uint16_t maxOverrun = 0;
+  uint32_t totalOverrun = 0;
+  uint32_t fifoBuf[128*FIFO_SIZE_SECTORS];
+  data_t* fifoData = (data_t*)fifoBuf;
+
+  // Write dummy sector to start multi-block write.
+  dbgAssert(sizeof(fifoBuf) >= 512);
+  memset(fifoBuf, 0, sizeof(fifoBuf));
+  if (binFile.write(fifoBuf, 512) != 512) {
+    error("write first sector failed");
+  }
+  clearSerialInput();
+  Serial.println(F("Type any character to stop"));
+
+  // Wait until SD is not busy.
+  while (sd.card()->isBusy()) {}
+
+  // Start time for log file.
+  uint32_t m = millis();
+
+  // Time to log next record.
+  uint32_t logTime = micros();
+  while (true) {
+    // Time for next data record.
+    logTime += LOG_INTERVAL_USEC;
+
+    // Wait until time to log data.
+    delta = micros() - logTime;
+    if (delta > 0) {
+      Serial.print(F("delta: "));
+      Serial.println(delta);
+      error("Rate too fast");
+    }
+    while (delta < 0) {
+      delta = micros() - logTime;
+    }
+
+    if (fifoCount < FIFO_DIM) {
+      uint32_t m = micros();
+      logRecord(fifoData + fifoHead, overrun);
+      m = micros() - m;
+      if (m > maxLogMicros) {
+        maxLogMicros = m;
+      }
+      fifoHead = fifoHead < (FIFO_DIM - 1) ? fifoHead + 1 : 0;
+      fifoCount++;
+      if (overrun) {
+        if (overrun > maxOverrun) {
+          maxOverrun = overrun;
+        }
+        overrun = 0;
+      }
+    } else {
+      totalOverrun++;
+      overrun++;
+      if (overrun > 0XFFF) {
+        error("too many overruns");
+      }
+      if (ERROR_LED_PIN >= 0) {
+        digitalWrite(ERROR_LED_PIN, HIGH);
+      }
+    }
+    // Save max jitter.
+    if (delta > maxDelta) {
+      maxDelta = delta;
+    }
+    // Write data if SD is not busy.
+    if (!sd.card()->isBusy()) {
+      size_t nw = fifoHead > fifoTail ? fifoCount : FIFO_DIM - fifoTail;
+      // Limit write time by not writing more than 512 bytes.
+      const size_t MAX_WRITE = 512/sizeof(data_t);
+      if (nw > MAX_WRITE) nw = MAX_WRITE;
+      size_t nb = nw*sizeof(data_t);
+      uint32_t usec = micros();
+      if (nb != binFile.write(fifoData + fifoTail, nb)) {
+        error("write binFile failed");
+      }
+      usec = micros() - usec;
+      if (usec > maxWriteMicros) {
+        maxWriteMicros = usec;
+      }
+      fifoTail = (fifoTail + nw) < FIFO_DIM ? fifoTail + nw : 0;
+      if (fifoCount > maxFifoUse) {
+        maxFifoUse = fifoCount;
+      }
+      fifoCount -= nw;
+      if (Serial.available()) {
+        break;
+      }
+    }
+  }
+  Serial.print(F("\nLog time: "));
+  Serial.print(0.001*(millis() - m));
+  Serial.println(F(" Seconds"));
+  binFile.truncate();
+  binFile.sync();
+  Serial.print(("File size: "));
+  // Warning cast used for print since fileSize is uint64_t.
+  Serial.print((uint32_t)binFile.fileSize());
+  Serial.println(F(" bytes"));
+  Serial.print(F("totalOverrun: "));
+  Serial.println(totalOverrun);
+  Serial.print(F("FIFO_DIM: "));
+  Serial.println(FIFO_DIM);
+  Serial.print(F("maxFifoUse: "));
+  Serial.println(maxFifoUse);
+  Serial.print(F("maxLogMicros: "));
+  Serial.println(maxLogMicros);
+  Serial.print(F("maxWriteMicros: "));
+  Serial.println(maxWriteMicros);
+  Serial.print(F("Log interval: "));
+  Serial.print(LOG_INTERVAL_USEC);
+  Serial.print(F(" micros\nmaxDelta: "));
+  Serial.print(maxDelta);
+  Serial.println(F(" micros"));
+}
+//------------------------------------------------------------------------------
+void openBinFile() {
+  char name[FILE_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_RDONLY)) {
+    Serial.println(name);
+    Serial.println(F("open failed"));
+    return;
+  }
+  Serial.println(F("File opened"));
+}
+//-----------------------------------------------------------------------------
+void printData() {
+  if (!binFile.isOpen()) {
+    Serial.println(F("No current binary file"));
+    return;
+  }
+  // Skip first dummy sector.
+  if (!binFile.seekSet(512)) {
+    error("seek failed");
+  }
+  clearSerialInput();
+  Serial.println(F("type any character to stop\n"));
+  delay(1000);
+  printRecord(&Serial, nullptr);
+  while (binFile.available() && !Serial.available()) {
+    data_t record;
+    if (binFile.read(&record, sizeof(data_t)) != sizeof(data_t)) {
+      error("read binFile failed");
+    }
+    printRecord(&Serial, &record);
+  }
+}
+//------------------------------------------------------------------------------
+void printUnusedStack() {
+#if HAS_UNUSED_STACK
+  Serial.print(F("\nUnused stack: "));
+  Serial.println(UnusedStack());
+#endif  // HAS_UNUSED_STACK
+}
+//------------------------------------------------------------------------------
+bool serialReadLine(char* str, size_t size) {
+  size_t n = 0;
+  while(!Serial.available()) {
+    yield();
+  }
+  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 testSensor() {
+  const uint32_t interval = 200000;
+  int32_t diff;
+  data_t data;
+  clearSerialInput();
+  Serial.println(F("\nTesting - type any character to stop\n"));
+  delay(1000);
+  printRecord(&Serial, nullptr);
+  uint32_t m = micros();
+  while (!Serial.available()) {
+    m += interval;
+    do {
+      diff = m - micros();
+    } while (diff > 0);
+    logRecord(&data, 0);
+    printRecord(&Serial, &data);
+  }
+}
+//------------------------------------------------------------------------------
+void setup() {
+  if (ERROR_LED_PIN >= 0) {
+    pinMode(ERROR_LED_PIN, OUTPUT);
+    digitalWrite(ERROR_LED_PIN, HIGH);
+  }
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  delay(1000);
+  Serial.println(F("Type any character to begin"));
+  while (!Serial.available()) {
+    yield();
+  }
+  FillStack();
+#if !ENABLE_DEDICATED_SPI
+  Serial.println(F(
+    "\nFor best performance edit SdFatConfig.h\n"
+    "and set ENABLE_DEDICATED_SPI nonzero"));
+#endif  // !ENABLE_DEDICATED_SPI
+
+  Serial.print(FIFO_DIM);
+  Serial.println(F(" FIFO entries will be used."));
+
+  // Initialize SD.
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+  }
+#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!");
+  }
+  // Set callback
+  FsDateTime::setCallback(dateTime);
+#endif  // USE_RTC
+}
+//------------------------------------------------------------------------------
+void loop() {
+  printUnusedStack();
+  // Read any Serial data.
+  clearSerialInput();
+
+  if (ERROR_LED_PIN >= 0) {
+    digitalWrite(ERROR_LED_PIN, LOW);
+  }
+  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 data"));
+  Serial.println(F("t - test without logging"));
+  while(!Serial.available()) {
+    yield();
+  }
+  char c = tolower(Serial.read());
+  Serial.println();
+
+  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 if (c == 't') {
+    testSensor();
+  } else {
+    Serial.println(F("Invalid entry"));
+  }
+}

+ 58 - 0
lib/SdFat_NoArduino/examples/MinimumSizeSdReader/MinimumSizeSdReader.ino

@@ -0,0 +1,58 @@
+// Create a text file on the SD with this path using short 8.3 names.
+#define SFN_PATH "/DIR/TEST.TXT"
+
+// Modify CS_PIN for your chip select pin.
+#define CS_PIN SS
+
+// Set USE_SD_H to one for SD.h or zero for SdFat.
+#define USE_SD_H 0
+
+#if USE_SD_H
+#include "SD.h"
+File file;
+#else
+#include "SdFat.h"
+// Setting ENABLE_DEDICATED_SPI to zero saves over 200 more bytes.
+#if ENABLE_DEDICATED_SPI
+#warning "Set ENABLE_DEDICATED_SPI zero in SdFat/src/SdFatConfig.h for minimum size"
+#endif  // ENABLE_DEDICATED_SPI
+// Insure FAT16/FAT32 only.
+SdFat32 SD;
+// FatFile does not support Stream functions, just simple read/write.
+FatFile file;
+#endif
+
+void error(const char* msg) {
+  Serial.println(msg);
+  while(true);
+}
+
+void setup() {
+  int n;
+  char buf[4];
+
+  Serial.begin(9600);
+  while (!Serial) {}
+  Serial.println("Type any character to begin");
+  while (!Serial.available()) {}
+
+  if (!SD.begin(CS_PIN)) error("SD.begin");
+
+#if USE_SD_H
+  file = SD.open(SFN_PATH);
+  if (!file) error("open");
+#else
+  // Open existing file with a path of 8.3 names.
+  // Directories will be opened O_RDONLY files O_RDWR.
+  if (!file.openExistingSFN(SFN_PATH)) error("open");
+#endif
+  while ((n = file.read(buf, sizeof(buf)))) {
+   Serial.write(buf, n);
+  }
+// close() is only needed if you write to the file. For example, read
+// config data, modify the data, rewind the file and write the data.
+// file.close();
+}
+
+void loop() {
+}

+ 105 - 0
lib/SdFat_NoArduino/examples/OpenNext/OpenNext.ino

@@ -0,0 +1,105 @@
+/*
+ * Print size, modify date/time, and name for all files in root.
+ */
+#include "SdFat.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
+
+#if SD_FAT_TYPE == 0
+SdFat sd;
+File dir;
+File file;
+#elif SD_FAT_TYPE == 1
+SdFat32 sd;
+File32 dir;
+File32 file;
+#elif SD_FAT_TYPE == 2
+SdExFat sd;
+ExFile dir;
+ExFile file;
+#elif SD_FAT_TYPE == 3
+SdFs sd;
+FsFile dir;
+FsFile file;
+#else  // SD_FAT_TYPE
+#error invalid SD_FAT_TYPE
+#endif  // SD_FAT_TYPE
+//------------------------------------------------------------------------------
+// Store error strings in flash to save RAM.
+#define error(s) sd.errorHalt(&Serial, F(s))
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+
+  Serial.println("Type any character to start");
+  while (!Serial.available()) {
+    yield();
+  }
+
+  // Initialize the SD.
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+  }
+  // Open root directory
+  if (!dir.open("/")){
+    error("dir.open failed");
+  }
+  // Open next file in root.
+  // Warning, openNext starts at the current position of dir so a
+  // rewind may be necessary in your application.
+  while (file.openNext(&dir, O_RDONLY)) {
+    file.printFileSize(&Serial);
+    Serial.write(' ');
+    file.printModifyDateTime(&Serial);
+    Serial.write(' ');
+    file.printName(&Serial);
+    if (file.isDir()) {
+      // Indicate a directory.
+      Serial.write('/');
+    }
+    Serial.println();
+    file.close();
+  }
+  if (dir.getError()) {
+    Serial.println("openNext failed");
+  } else {
+    Serial.println("Done!");
+  }
+}
+//------------------------------------------------------------------------------
+void loop() {}

+ 187 - 0
lib/SdFat_NoArduino/examples/QuickStart/QuickStart.ino

@@ -0,0 +1,187 @@
+// Quick hardware test for SPI card access.
+//
+#include <SPI.h>
+#include "SdFat.h"
+#include "sdios.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 3
+//
+// Set DISABLE_CHIP_SELECT to disable a second SPI device.
+// For example, with the Ethernet shield, set DISABLE_CHIP_SELECT
+// to 10 to disable the Ethernet controller.
+const int8_t DISABLE_CHIP_SELECT = -1;
+//
+// Test with reduced SPI speed for breadboards.  SD_SCK_MHZ(4) will select
+// the highest speed supported by the board that is not over 4 MHz.
+// Change SPI_SPEED to SD_SCK_MHZ(50) for best performance.
+#define SPI_SPEED SD_SCK_MHZ(4)
+//------------------------------------------------------------------------------
+#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 streams
+ArduinoOutStream cout(Serial);
+
+// input buffer for line
+char cinBuf[40];
+ArduinoInStream cin(Serial, cinBuf, sizeof(cinBuf));
+
+// SD card chip select
+int chipSelect;
+
+void cardOrSpeed() {
+  cout << F("Try another SD card or reduce the SPI bus speed.\n");
+  cout << F("Edit SPI_SPEED in this program to change it.\n");
+}
+
+void clearSerialInput() {
+  uint32_t m = micros();
+  do {
+    if (Serial.read() >= 0) {
+      m = micros();
+    }
+  } while (micros() - m < 10000);
+}
+
+void reformatMsg() {
+  cout << F("Try reformatting the card.  For best results use\n");
+  cout << F("the SdFormatter program in SdFat/examples or download\n");
+  cout << F("and use SDFormatter from www.sdcard.org/downloads.\n");
+}
+
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  cout << F("\nSPI pins:\n");
+  cout << F("MISO: ") << int(MISO) << endl;
+  cout << F("MOSI: ") << int(MOSI) << endl;
+  cout << F("SCK:  ") << int(SCK) << endl;
+  cout << F("SS:   ") << int(SS) << endl;
+#ifdef SDCARD_SS_PIN
+  cout << F("SDCARD_SS_PIN:   ") << int(SDCARD_SS_PIN) << endl;
+#endif  // SDCARD_SS_PIN
+
+  if (DISABLE_CHIP_SELECT < 0) {
+    cout << F(
+           "\nBe sure to edit DISABLE_CHIP_SELECT if you have\n"
+           "a second SPI device.  For example, with the Ethernet\n"
+           "shield, DISABLE_CHIP_SELECT should be set to 10\n"
+           "to disable the Ethernet controller.\n");
+  }
+  cout << F(
+         "\nSD chip select is the key hardware option.\n"
+         "Common values are:\n"
+         "Arduino Ethernet shield, pin 4\n"
+         "Sparkfun SD shield, pin 8\n"
+         "Adafruit SD shields and modules, pin 10\n");
+}
+
+bool firstTry = true;
+void loop() {
+  // Read any existing Serial data.
+  clearSerialInput();
+
+  if (!firstTry) {
+    cout << F("\nRestarting\n");
+  }
+  firstTry = false;
+
+  cout << F("\nEnter the chip select pin number: ");
+  while (!Serial.available()) {
+    yield();
+  }
+  cin.readline();
+  if (cin >> chipSelect) {
+    cout << chipSelect << endl;
+  } else {
+    cout << F("\nInvalid pin number\n");
+    return;
+  }
+  if (DISABLE_CHIP_SELECT < 0) {
+    cout << F(
+           "\nAssuming the SD is the only SPI device.\n"
+           "Edit DISABLE_CHIP_SELECT to disable another device.\n");
+  } else {
+    cout << F("\nDisabling SPI device on pin ");
+    cout << int(DISABLE_CHIP_SELECT) << endl;
+    pinMode(DISABLE_CHIP_SELECT, OUTPUT);
+    digitalWrite(DISABLE_CHIP_SELECT, HIGH);
+  }
+  if (!sd.begin(chipSelect, SPI_SPEED)) {
+    if (sd.card()->errorCode()) {
+      cout << F(
+             "\nSD initialization failed.\n"
+             "Do not reformat the card!\n"
+             "Is the card correctly inserted?\n"
+             "Is chipSelect set to the correct value?\n"
+             "Does another SPI device need to be disabled?\n"
+             "Is there a wiring/soldering problem?\n");
+      cout << F("\nerrorCode: ") << hex << showbase;
+      cout << int(sd.card()->errorCode());
+      cout << F(", errorData: ") << int(sd.card()->errorData());
+      cout << dec << noshowbase << endl;
+      return;
+    }
+    cout << F("\nCard successfully initialized.\n");
+    if (sd.vol()->fatType() == 0) {
+      cout << F("Can't find a valid FAT16/FAT32 partition.\n");
+      reformatMsg();
+      return;
+    }
+    cout << F("Can't determine error type\n");
+    return;
+  }
+  cout << F("\nCard successfully initialized.\n");
+  cout << endl;
+
+  uint32_t size = sd.card()->sectorCount();
+  if (size == 0) {
+    cout << F("Can't determine the card size.\n");
+    cardOrSpeed();
+    return;
+  }
+  uint32_t sizeMB = 0.000512 * size + 0.5;
+  cout << F("Card size: ") << sizeMB;
+  cout << F(" MB (MB = 1,000,000 bytes)\n");
+  cout << endl;
+  cout << F("Volume is FAT") << int(sd.vol()->fatType());
+  cout << F(", Cluster size (bytes): ") << sd.vol()->bytesPerCluster();
+  cout << endl << endl;
+
+  cout << F("Files found (date time size name):\n");
+  sd.ls(LS_R | LS_DATE | LS_SIZE);
+
+  if ((sizeMB > 1100 && sd.vol()->sectorsPerCluster() < 64)
+      || (sizeMB < 2200 && sd.vol()->fatType() == 32)) {
+    cout << F("\nThis card should be reformatted for best performance.\n");
+    cout << F("Use a cluster size of 32 KB for cards larger than 1 GB.\n");
+    cout << F("Only cards larger than 2 GB should be formatted FAT32.\n");
+    reformatMsg();
+    return;
+  }
+  // Read any extra Serial data.
+  clearSerialInput();
+
+  cout << F("\nSuccess!  Type any character to restart.\n");
+  while (!Serial.available()) {
+    yield();
+  }
+}

+ 156 - 0
lib/SdFat_NoArduino/examples/ReadCsvFile/ReadCsvFile.ino

@@ -0,0 +1,156 @@
+#include "SdFat.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
+
+#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
+
+char line[40];
+
+//------------------------------------------------------------------------------
+// Store error strings in flash to save RAM.
+#define error(s) sd.errorHalt(&Serial, F(s))
+//------------------------------------------------------------------------------
+// Check for extra characters in field or find minus sign.
+char* skipSpace(char* str) {
+  while (isspace(*str)) str++;
+  return str;
+}
+//------------------------------------------------------------------------------
+bool parseLine(char* str) {
+  char* ptr;
+
+  // Set strtok start of line.
+  str = strtok(str, ",");
+  if (!str) return false;
+
+  // Print text field.
+  Serial.println(str);
+
+  // Subsequent calls to strtok expects a null pointer.
+  str = strtok(nullptr, ",");
+  if (!str) return false;
+
+  // Convert string to long integer.
+  int32_t i32 = strtol(str, &ptr, 0);
+  if (str == ptr || *skipSpace(ptr)) return false;
+  Serial.println(i32);
+
+  str = strtok(nullptr, ",");
+  if (!str) return false;
+
+  // strtoul accepts a leading minus with unexpected results.
+  if (*skipSpace(str) == '-') return false;
+
+  // Convert string to unsigned long integer.
+  uint32_t u32 = strtoul(str, &ptr, 0);
+  if (str == ptr || *skipSpace(ptr)) return false;
+  Serial.println(u32);
+
+  str = strtok(nullptr, ",");
+  if (!str) return false;
+
+  // Convert string to double.
+  double d = strtod(str, &ptr);
+  if (str == ptr || *skipSpace(ptr)) return false;
+  Serial.println(d);
+
+  // Check for extra fields.
+  return strtok(nullptr, ",") == nullptr;
+}
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  Serial.println("Type any character to start");
+  while (!Serial.available()) {
+    yield();
+  }
+  // Initialize the SD.
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+    return;
+  }
+  // Remove any existing file.
+  if (sd.exists("ReadCsvDemo.csv")) {
+    sd.remove("ReadCsvDemo.csv");
+  }
+  // Create the file.
+  if (!file.open("ReadCsvDemo.csv", FILE_WRITE)) {
+    error("open failed");
+  }
+  // Write test data.
+  file.print(F(
+    "abc,123,456,7.89\r\n"
+    "def,-321,654,-9.87\r\n"
+    "ghi,333,0xff,5.55"));
+
+  // Rewind file for read.
+  file.rewind();
+
+  while (file.available()) {
+    int n = file.fgets(line, sizeof(line));
+    if (n <= 0) {
+      error("fgets failed");
+    }
+    if (line[n-1] != '\n' && n == (sizeof(line) - 1)) {
+      error("line too long");
+    }
+    if (!parseLine(line)) {
+      error("parseLine failed");
+    }
+    Serial.println();
+  }
+  file.close();
+  Serial.println(F("Done"));
+}
+
+void loop() {
+}

+ 236 - 0
lib/SdFat_NoArduino/examples/RtcTimestampTest/RtcTimestampTest.ino

@@ -0,0 +1,236 @@
+// Test of time-stamp callback.
+// Set the callback with this statement.
+// FsDateTime::setCallback(dateTime);
+#include "SdFat.h"
+// https://github.com/adafruit/RTClib
+#include "RTClib.h"
+// Set RTC_TYPE for file timestamps.
+// 0 - millis()
+// 1 - DS1307
+// 2 - DS3231
+// 3 - PCF8523
+#define RTC_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 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
+
+#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
+
+
+#if RTC_TYPE == 0
+RTC_Millis rtc;
+#elif RTC_TYPE == 1
+RTC_DS1307 rtc;
+#elif RTC_TYPE == 2
+RTC_DS3231 rtc;
+#elif RTC_TYPE == 3
+RTC_PCF8523 rtc;
+#else  // RTC_TYPE == type
+#error RTC_TYPE type not implemented.
+#endif  // RTC_TYPE == 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, 0 <= ms10 <= 199.
+  *ms10 = now.second() & 1 ? 100 : 0;
+}
+//------------------------------------------------------------------------------
+#define error(msg) (Serial.println(F("error " msg)), false)
+//------------------------------------------------------------------------------
+void clearSerialInput() {
+  uint32_t m = micros();
+  do {
+    if (Serial.read() >= 0) {
+      m = micros();
+    }
+  } while (micros() - m < 10000);
+}
+//------------------------------------------------------------------------------
+void getLine(char* line, size_t size) {
+  size_t i = 0;
+  uint32_t t;
+  line[0] = '\0';
+  while (!Serial.available()) {
+    yield();
+  }
+  while (true) {
+    t = millis() + 10;
+    while (!Serial.available()) {
+      if (millis() > t){
+        return;
+      }
+    }
+    int c = Serial.read();
+    if (i >= (size - 1) || c == '\r' || c == '\n' ) {
+      return;
+    }
+    line[i++] = c;
+    line[i] = '\0';
+  }
+}
+//------------------------------------------------------------------------------
+void printField(Print* pr, char sep, uint8_t v) {
+  if (sep) {
+    pr->write(sep);
+  }
+  if (v < 10) {
+    pr->write('0');
+  }
+  pr->print(v);
+}
+//------------------------------------------------------------------------------
+void printNow(Print* pr) {
+  DateTime now = rtc.now();
+  pr->print(now.year());
+  printField(pr, '-',now.month());
+  printField(pr, '-',now.day());
+  printField(pr, ' ',now.hour());
+  printField(pr, ':',now.minute());
+  printField(pr, ':',now.second());
+}
+//------------------------------------------------------------------------------
+bool setRtc() {
+  uint16_t y;
+  uint8_t m, d, hh, mm, ss;
+  char line[30];
+  char* ptr;
+
+  clearSerialInput();
+  Serial.println(F("Enter: YYYY-MM-DD hh:mm:ss"));
+  getLine(line, sizeof(line));
+  Serial.print(F("Input: "));
+  Serial.println(line);
+
+  y = strtol(line, &ptr, 10);
+  if (*ptr++ != '-' || y < 2000 || y > 2099) return error("year");
+  m = strtol(ptr, &ptr, 10);
+  if (*ptr++ != '-' || m < 1 || m > 12) return error("month");
+  d = strtol(ptr, &ptr, 10);
+  if (d < 1 || d > 31) return error("day");
+  hh = strtol(ptr, &ptr, 10);
+  if (*ptr++ != ':' || hh > 23) return error("hour");
+  mm = strtol(ptr, &ptr, 10);
+  if (*ptr++ != ':' || mm > 59) return error("minute");
+  ss = strtol(ptr, &ptr, 10);
+  if (ss > 59) return error("second");
+
+  rtc.adjust(DateTime(y, m, d, hh, mm, ss));
+  Serial.print(F("RTC set to "));
+  printNow(&Serial);
+  Serial.println();
+  return true;
+}
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+  while (!Serial) {
+    yield();
+  }
+#if RTC_TYPE == 0
+  rtc.begin(DateTime(F(__DATE__), F(__TIME__)));
+#else  // RTC_TYPE
+  if (!rtc.begin()) {
+    Serial.println(F("rtc.begin failed"));
+    return;
+  }
+  if (!rtc.isrunning()) {
+    Serial.println(F("RTC is NOT running!"));
+    return;
+    // following line sets the RTC to the date & time this sketch was compiled
+    // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
+    // This line sets the RTC with an explicit date & time, for example to set
+    // January 21, 2014 at 3am you would call:
+    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
+  }
+#endif  // RTC_TYPE
+  while (true) {
+    Serial.print(F("DateTime::now "));
+    printNow(&Serial);
+    Serial.println();
+    clearSerialInput();
+    Serial.println(F("Type Y to set RTC, any other character to continue"));
+    while (!Serial.available()) {}
+    if (Serial.read() != 'Y') break;
+    if (setRtc()) break;
+  }
+  Serial.println();
+
+  // Set callback
+  FsDateTime::setCallback(dateTime);
+
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+  }
+  // Remove old version to set create time.
+  if (sd.exists("RtcTest.txt")) {
+    sd.remove("RtcTest.txt");
+  }
+  if (!file.open("RtcTest.txt", FILE_WRITE)) {
+    Serial.println(F("file.open failed"));
+    return;
+  }
+  // Print current date time to file.
+  file.print(F("Test file at: "));
+  printNow(&file);
+  file.println();
+
+  file.close();
+  // List files in SD root.
+  sd.ls(LS_DATE | LS_SIZE);
+  Serial.println(F("Done"));
+}
+//------------------------------------------------------------------------------
+void loop() {
+}

+ 19 - 0
lib/SdFat_NoArduino/examples/SdErrorCodes/SdErrorCodes.ino

@@ -0,0 +1,19 @@
+// Print a list of error codes, symbols, and comments.
+#include "SdFat.h"
+void setup() {
+  Serial.begin(9600);
+  while (!Serial) {}
+  delay(1000);
+  Serial.println();
+  Serial.println(F("Code,Symbol - failed operation"));
+  for (uint8_t code = 0; code <= SD_CARD_ERROR_UNKNOWN; code++) {
+    Serial.print(code < 16 ? "0X0" : "0X");
+    Serial.print(code, HEX);
+    Serial.print(",");
+    printSdErrorSymbol(&Serial, code);
+    Serial.print(" - ");
+    printSdErrorText(&Serial, code);
+    Serial.println();
+  }
+}
+void loop() {}

+ 249 - 0
lib/SdFat_NoArduino/examples/SdFormatter/SdFormatter.ino

@@ -0,0 +1,249 @@
+/*
+ * This program will format SD/SDHC/SDXC cards.
+ * Warning all data will be deleted!
+ *
+ * This program attempts to match the format
+ * generated by SDFormatter available here:
+ *
+ * http://www.sdcard.org/consumers/formatter/
+ *
+ * For very small cards this program uses FAT16
+ * and the above SDFormatter uses FAT12.
+ */
+#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
+// 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
+//==============================================================================
+// Serial output stream
+ArduinoOutStream cout(Serial);
+//------------------------------------------------------------------------------
+uint32_t cardSectorCount = 0;
+uint8_t  sectorBuffer[512];
+//------------------------------------------------------------------------------
+// SdCardFactory constructs and initializes the appropriate card.
+SdCardFactory cardFactory;
+// Pointer to generic SD card.
+SdCard* m_card = nullptr;
+//------------------------------------------------------------------------------
+#define sdError(msg) {cout << F("error: ") << F(msg) << endl; sdErrorHalt();}
+//------------------------------------------------------------------------------
+void sdErrorHalt() {
+  if (!m_card) {
+    cout << F("Invalid SD_CONFIG") << endl;
+  } else if (m_card->errorCode()) {
+    if (m_card->errorCode() == SD_CARD_ERROR_CMD0) {
+      cout << F("No card, wrong chip select pin, or wiring error?") << endl;
+    }
+    cout << F("SD errorCode: ") << hex << showbase;
+    printSdErrorSymbol(&Serial, m_card->errorCode());
+    cout << F(" = ") << int(m_card->errorCode()) << endl;
+    cout << F("SD errorData = ") << int(m_card->errorData()) << endl;
+  }
+  while (true) {}
+}
+//------------------------------------------------------------------------------
+void clearSerialInput() {
+  uint32_t m = micros();
+  do {
+    if (Serial.read() >= 0) {
+      m = micros();
+    }
+  } while (micros() - m < 10000);
+}
+//------------------------------------------------------------------------------
+// flash erase all data
+uint32_t const ERASE_SIZE = 262144L;
+void eraseCard() {
+  cout << endl << F("Erasing\n");
+  uint32_t firstBlock = 0;
+  uint32_t lastBlock;
+  uint16_t n = 0;
+
+  do {
+    lastBlock = firstBlock + ERASE_SIZE - 1;
+    if (lastBlock >= cardSectorCount) {
+      lastBlock = cardSectorCount - 1;
+    }
+    if (!m_card->erase(firstBlock, lastBlock)) {
+      sdError("erase failed");
+    }
+    cout << '.';
+    if ((n++)%64 == 63) {
+      cout << endl;
+    }
+    firstBlock += ERASE_SIZE;
+  } while (firstBlock < cardSectorCount);
+  cout << endl;
+
+  if (!m_card->readSector(0, sectorBuffer)) {
+    sdError("readBlock");
+  }
+  cout << hex << showbase << setfill('0') << internal;
+  cout << F("All data set to ") << setw(4) << int(sectorBuffer[0]) << endl;
+  cout << dec << noshowbase << setfill(' ') << right;
+  cout << F("Erase done\n");
+}
+//------------------------------------------------------------------------------
+void formatCard() {
+  ExFatFormatter exFatFormatter;
+  FatFormatter fatFormatter;
+
+  // Format exFAT if larger than 32GB.
+  bool rtn = cardSectorCount > 67108864 ?
+    exFatFormatter.format(m_card, sectorBuffer, &Serial) :
+    fatFormatter.format(m_card, sectorBuffer, &Serial);
+
+  if (!rtn) {
+    sdErrorHalt();
+  }
+  cout << F("Run the SdInfo example for format details.") << endl;
+}
+//------------------------------------------------------------------------------
+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() {
+  char c;
+  Serial.begin(9600);
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  printConfig(SD_CONFIG);
+  cout << F("\nType any character to start\n");
+  while (!Serial.available()) {
+    yield();
+  }
+  // Discard any extra characters.
+  clearSerialInput();
+
+  cout << F(
+         "\n"
+         "This program can erase and/or format SD/SDHC/SDXC cards.\n"
+         "\n"
+         "Erase uses the card's fast flash erase command.\n"
+         "Flash erase sets all data to 0X00 for most cards\n"
+         "and 0XFF for a few vendor's cards.\n"
+         "\n"
+         "Cards up to 2 GiB (GiB = 2^30 bytes) will be formated FAT16.\n"
+         "Cards larger than 2 GiB and up to 32 GiB will be formatted\n"
+         "FAT32. Cards larger than 32 GiB will be formatted exFAT.\n"
+         "\n"
+         "Warning, all data on the card will be erased.\n"
+         "Enter 'Y' to continue: ");
+  while (!Serial.available()) {
+    yield();
+  }
+  c = Serial.read();
+  cout << c << endl;
+  if (c != 'Y') {
+    cout << F("Quiting, you did not enter 'Y'.\n");
+    return;
+  }
+  // Read any existing Serial data.
+  clearSerialInput();
+
+  // Select and initialize proper card driver.
+  m_card = cardFactory.newCard(SD_CONFIG);
+  if (!m_card || m_card->errorCode()) {
+    sdError("card init failed.");
+    return;
+  }
+
+  cardSectorCount = m_card->sectorCount();
+  if (!cardSectorCount) {
+    sdError("Get sector count failed.");
+    return;
+  }
+
+  cout << F("\nCard size: ") << cardSectorCount*5.12e-7;
+  cout << F(" GB (GB = 1E9 bytes)\n");
+  cout << F("Card size: ") << cardSectorCount/2097152.0;
+  cout << F(" GiB (GiB = 2^30 bytes)\n");
+
+  cout << F("Card will be formated ");
+  if (cardSectorCount > 67108864) {
+    cout << F("exFAT\n");
+  } else if (cardSectorCount > 4194304) {
+    cout << F("FAT32\n");
+  } else {
+    cout << F("FAT16\n");
+  }
+  cout << F(
+         "\n"
+         "Options are:\n"
+         "E - erase the card and skip formatting.\n"
+         "F - erase and then format the card. (recommended)\n"
+         "Q - quick format the card without erase.\n"
+         "\n"
+         "Enter option: ");
+
+  while (!Serial.available()) {
+    yield();
+  }
+  c = Serial.read();
+  cout << c << endl;
+  if (!strchr("EFQ", c)) {
+    cout << F("Quiting, invalid option entered.") << endl;
+    return;
+  }
+  if (c == 'E' || c == 'F') {
+    eraseCard();
+  }
+  if (c == 'F' || c == 'Q') {
+    formatCard();
+  }
+}
+void loop() {
+}

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

@@ -0,0 +1,265 @@
+/*
+ * 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 m_cid;
+csd_t m_csd;
+uint32_t m_eraseSize;
+uint32_t m_ocr;
+static ArduinoOutStream cout(Serial);
+//------------------------------------------------------------------------------
+bool cidDmp() {
+  cout << F("\nManufacturer ID: ");
+  cout << uppercase << showbase << hex << int(m_cid.mid) << dec << endl;
+  cout << F("OEM ID: ") << m_cid.oid[0] << m_cid.oid[1] << endl;
+  cout << F("Product: ");
+  for (uint8_t i = 0; i < 5; i++) {
+    cout << m_cid.pnm[i];
+  }
+  cout << F("\nVersion: ");
+  cout << int(m_cid.prv_n) << '.' << int(m_cid.prv_m) << endl;
+  cout << F("Serial number: ") << hex << m_cid.psn << dec << endl;
+  cout << F("Manufacturing date: ");
+  cout << int(m_cid.mdt_month) << '/';
+  cout << (2000 + 16*m_cid.mdt_year_high + m_cid.mdt_year_low) << endl;
+  cout << endl;
+  return true;
+}
+//------------------------------------------------------------------------------
+void clearSerialInput() {
+  uint32_t m = micros();
+  do {
+    if (Serial.read() >= 0) {
+      m = micros();
+    }
+  } while (micros() - m < 10000);
+}
+//------------------------------------------------------------------------------
+bool csdDmp() {
+  bool eraseSingleBlock;
+  if (m_csd.v1.csd_ver == 0) {
+    eraseSingleBlock = m_csd.v1.erase_blk_en;
+    m_eraseSize = (m_csd.v1.sector_size_high << 1) | m_csd.v1.sector_size_low;
+  } else if (m_csd.v2.csd_ver == 1) {
+    eraseSingleBlock = m_csd.v2.erase_blk_en;
+    m_eraseSize = (m_csd.v2.sector_size_high << 1) | m_csd.v2.sector_size_low;
+  } else {
+    cout << F("m_csd version error\n");
+    return false;
+  }
+  m_eraseSize++;
+  cout << F("cardSize: ") << 0.000512 * sdCardCapacity(&m_csd);
+  cout << F(" MB (MB = 1,000,000 bytes)\n");
+
+  cout << F("flashEraseSize: ") << int(m_eraseSize) << F(" blocks\n");
+  cout << F("eraseSingleBlock: ");
+  if (eraseSingleBlock) {
+    cout << F("true\n");
+  } else {
+    cout << F("false\n");
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+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()) << 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) > sdCardCapacity(&m_csd)) {
+      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");
+  uint32_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("clusterCount:      ") << sd.clusterCount() << endl;
+  cout << F("freeClusterCount:  ") << freeClusterCount << endl;
+  cout << F("fatStartSector:    ") << sd.fatStartSector() << endl;
+  cout << F("dataStartSector:   ") << sd.dataStartSector() << endl;
+  if (sd.dataStartSector() % m_eraseSize) {
+    cout << F("Data area is not aligned on flash erase boundary!\n");
+    cout << F("Download and use formatter from www.sdcard.org!\n");
+  }
+}
+//------------------------------------------------------------------------------
+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 (sdCardCapacity(&m_csd) < 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: ") << t << " ms" << endl;
+
+  if (!sd.card()->readCID(&m_cid) ||
+      !sd.card()->readCSD(&m_csd) ||
+      !sd.card()->readOCR(&m_ocr)) {
+    cout << F("readInfo failed\n");
+    errorPrint();
+    return;
+  }
+  printCardType();
+  cidDmp();
+  csdDmp();
+  cout << F("\nOCR: ") << uppercase << showbase;
+  cout << hex << m_ocr << dec << endl;
+  if (!mbrDmp()) {
+    return;
+  }
+  if (!sd.volumeBegin()) {
+    cout << F("\nvolumeBegin failed. Is the card formatted?\n");
+    errorPrint();
+    return;
+  }
+  dmpVol();
+}

+ 80 - 0
lib/SdFat_NoArduino/examples/SoftwareSpi/SoftwareSpi.ino

@@ -0,0 +1,80 @@
+// An example of the SoftSpiDriver template class.
+// This example is for an old Adafruit Data Logging Shield on a Mega.
+// Software SPI is required on Mega since this shield connects to pins 10-13.
+// This example will also run on an Uno and other boards using software SPI.
+//
+#include "SdFat.h"
+#if SPI_DRIVER_SELECT == 2  // Must be set in SdFat/SdFatConfig.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
+//
+// Chip select may be constant or RAM variable.
+const uint8_t SD_CS_PIN = 10;
+//
+// Pin numbers in templates must be constants.
+const uint8_t SOFT_MISO_PIN = 12;
+const uint8_t SOFT_MOSI_PIN = 11;
+const uint8_t SOFT_SCK_PIN  = 13;
+
+// SdFat software SPI template
+SoftSpiDriver<SOFT_MISO_PIN, SOFT_MOSI_PIN, SOFT_SCK_PIN> softSpi;
+// Speed argument is ignored for software SPI.
+#if ENABLE_DEDICATED_SPI
+#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(0), &softSpi)
+#else  // ENABLE_DEDICATED_SPI
+#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(0), &softSpi)
+#endif  // ENABLE_DEDICATED_SPI
+
+#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
+
+void setup() {
+  Serial.begin(9600);
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  Serial.println("Type any character to start");
+  while (!Serial.available()) {
+    yield();
+  }
+
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt();
+  }
+
+  if (!file.open("SoftSPI.txt", O_RDWR | O_CREAT)) {
+    sd.errorHalt(F("open failed"));
+  }
+  file.println(F("This line was printed using software SPI."));
+
+  file.rewind();
+
+  while (file.available()) {
+    Serial.write(file.read());
+  }
+
+  file.close();
+
+  Serial.println(F("Done."));
+}
+//------------------------------------------------------------------------------
+void loop() {}
+#else  // SPI_DRIVER_SELECT
+#error SPI_DRIVER_SELECT must be two in SdFat/SdFatConfig.h
+#endif  //SPI_DRIVER_SELECT

+ 247 - 0
lib/SdFat_NoArduino/examples/TeensyDmaAdcLogger/TeensyDmaAdcLogger.ino

@@ -0,0 +1,247 @@
+// Test of Teensy exFAT DMA ADC logger.
+// This is mainly to test use of RingBuf in an ISR.
+// You should modify it for serious use as a data logger.
+//
+#include "DMAChannel.h"
+#include "SdFat.h"
+#include "FreeStack.h"
+#include "RingBuf.h"
+
+// 400 sector RingBuf - could be larger on Teensy 4.1.
+const size_t RING_BUF_SIZE = 400*512;
+
+// Preallocate 8GiB file.
+const uint64_t PRE_ALLOCATE_SIZE = 8ULL << 30;
+
+// Use FIFO SDIO.
+#define SD_CONFIG SdioConfig(FIFO_SDIO)
+
+DMAChannel dma(true);
+
+SdFs sd;
+
+FsFile file;
+//------------------------------------------------------------------------------
+// Ping-pong DMA buffer.
+DMAMEM static uint16_t __attribute__((aligned(32))) dmaBuf[2][256];
+size_t dmaCount;
+
+// RingBuf for 512 byte sectors.
+RingBuf<FsFile, RING_BUF_SIZE> rb;
+
+// Shared between ISR and background.
+volatile size_t maxBytesUsed;
+
+volatile bool overrun;
+//------------------------------------------------------------------------------
+//ISR.
+static void isr() {
+  if (rb.bytesFreeIsr() >= 512 && !overrun) {
+    rb.memcpyIn(dmaBuf[dmaCount & 1], 512);
+    dmaCount++;
+    if (rb.bytesUsed() > maxBytesUsed) {
+      maxBytesUsed = rb.bytesUsed();
+    }
+  } else {
+    overrun = true;
+  }
+  dma.clearComplete();
+  dma.clearInterrupt();
+#if defined(__IMXRT1062__)
+  // Handle clear interrupt glitch in Teensy 4.x!
+  asm("DSB");
+#endif  // defined(__IMXRT1062__)
+}
+//------------------------------------------------------------------------------
+// Over-clocking will degrade quality - use only for stress testing.
+void overclock() {
+#if defined(__IMXRT1062__) // Teensy 4.0
+  ADC1_CFG  =
+    // High Speed Configuration
+    ADC_CFG_ADHSC |
+    // Sample period 3 clocks
+    ADC_CFG_ADSTS(0) |
+    // Input clock
+    ADC_CFG_ADIV(0) |
+    // Not selected - Long Sample Time Configuration
+    // ADC_CFG_ADLSMP |
+    // 12-bit
+    ADC_CFG_MODE(2) |
+    // Asynchronous clock
+    ADC_CFG_ADICLK(3);
+#else // defined(__IMXRT1062__)
+  // Set 12 bit mode and max over-clock
+  ADC0_CFG1 =
+    // Clock divide select, 0=direct, 1=div2, 2=div4, 3=div8
+    ADC_CFG1_ADIV(0) |
+    // Sample time configuration, 0=Short, 1=Long
+    // ADC_CFG1_ADLSMP |
+    // Conversion mode, 0=8 bit, 1=12 bit, 2=10 bit, 3=16 bit
+    ADC_CFG1_MODE(1) |
+    // Input clock, 0=bus, 1=bus/2, 2=OSCERCLK, 3=async
+    ADC_CFG1_ADICLK(0);
+
+  ADC0_CFG2 = ADC_CFG2_MUXSEL | ADC_CFG2_ADLSTS(3);
+#endif  // defined(__IMXRT1062__)
+}
+//------------------------------------------------------------------------------
+#if defined(__IMXRT1062__) // Teensy 4.0
+#define SOURCE_SADDR ADC1_R0
+#define SOURCE_EVENT DMAMUX_SOURCE_ADC1
+#else
+#define SOURCE_SADDR ADC0_RA
+#define SOURCE_EVENT DMAMUX_SOURCE_ADC0
+#endif
+//------------------------------------------------------------------------------
+// Should replace ADC stuff with calls to Teensy ADC library.
+// https://github.com/pedvide/ADC
+static void init(uint8_t pin) {
+  uint32_t adch;
+	uint32_t i, sum = 0;
+	// Actually, do many normal reads, to start with a nice DC level
+	for (i=0; i < 1024; i++) {
+		sum += analogRead(pin);
+	}
+#if defined(__IMXRT1062__) // Teensy 4.0
+  // save channel
+  adch = ADC1_HC0 & 0x1F;
+  // Continuous conversion , DMA enable
+  ADC1_GC = ADC_GC_ADCO | ADC_GC_DMAEN;
+  // start conversion
+  ADC1_HC0 = adch;
+#else  // defined(__IMXRT1062__) // Teensy 4.0
+  // save channel
+  adch = ADC0_SC1A & 0x1F;
+  // DMA enable
+  ADC0_SC2 |= ADC_SC2_DMAEN;
+  // Continuous conversion enable
+  ADC0_SC3 = ADC_SC3_ADCO;
+  // Start ADC
+  ADC0_SC1A = adch;
+ #endif  // defined(__IMXRT1062__) // Teensy 4.0
+	// set up a DMA channel to store the ADC data
+ 	dma.attachInterrupt(isr);
+	dma.begin();
+  dma.source((volatile const signed short &)SOURCE_SADDR);
+  dma.destinationBuffer((volatile uint16_t*)dmaBuf, sizeof(dmaBuf));
+  dma.interruptAtHalf();
+  dma.interruptAtCompletion();
+	dma.triggerAtHardwareEvent(SOURCE_EVENT);
+	dma.enable();
+}
+//------------------------------------------------------------------------------
+void stopDma() {
+#if defined(__IMXRT1062__) // Teensy 4.0
+  ADC1_GC = 0;
+#else  // defined(__IMXRT1062__)
+  ADC0_SC3 = 0;
+#endif  // defined(__IMXRT1062__)
+  dma.disable();
+}
+//------------------------------------------------------------------------------
+void printTest(Print* pr) {
+  if (file.fileSize() < 1024*2) {
+    return;
+  }
+  file.rewind();
+  rb.begin(&file);
+  // Could readIn RING_BUF_SIZE bytes and write to a csv file in a loop.
+  if (rb.readIn(2048) != 2048) {
+    sd.errorHalt("rb.readIn failed");
+  }
+  uint16_t data;
+  for (size_t i = 0; i < 1024; i++) {
+    pr->print(i);
+    pr->print(',');
+    rb.memcpyOut(&data, 2);
+    pr->println(data);
+  }
+}
+//------------------------------------------------------------------------------
+void runTest(uint8_t pin) {
+  dmaCount = 0;
+  maxBytesUsed = 0;
+  overrun = false;
+  do {
+    delay(10);
+  } while (Serial.read() >= 0);
+
+  if (!file.open("IsrLoggerTest.bin", O_CREAT | O_TRUNC | O_RDWR)) {
+    sd.errorHalt("file.open failed");
+  }
+  if (!file.preAllocate(PRE_ALLOCATE_SIZE)) {
+    sd.errorHalt("file.preAllocate failed");
+  }
+  rb.begin(&file);
+  Serial.println("Type any character to stop\n");
+
+  init(pin);
+  uint32_t samplingTime = micros();
+  while (!overrun && !Serial.available()) {
+    size_t n = rb.bytesUsed();
+    if ((n + file.curPosition()) >= (PRE_ALLOCATE_SIZE - 512)) {
+      Serial.println("File full - stopping");
+      break;
+    }
+    if (n >= 512) {
+      if (rb.writeOut(512) != 512) {
+        Serial.println("writeOut() failed");
+        file.close();
+        return;
+      }
+    }
+  }
+  stopDma();
+  samplingTime = (micros() - samplingTime);
+  if (!file.truncate()) {
+    sd.errorHalt("truncate failed");
+  }
+  if (overrun) {
+    Serial.println("Overrun ERROR!!");
+  }
+  Serial.print("RingBufSize ");
+  Serial.println(RING_BUF_SIZE);
+  Serial.print("maxBytesUsed ");
+  Serial.println(maxBytesUsed);
+  Serial.print("fileSize ");
+  Serial.println((uint32_t)file.fileSize());
+  Serial.print(0.000001*samplingTime);
+  Serial.println(" seconds");
+  Serial.print(1.0*file.fileSize()/samplingTime, 3);
+  Serial.println(" MB/sec\n");
+  printTest(&Serial);
+  file.close();
+}
+//------------------------------------------------------------------------------
+void waitSerial(const char* msg) {
+  uint32_t m = micros();
+  do {
+    if (Serial.read() >= 0) {
+      m = micros();
+    }
+  } while (micros() - m < 10000);
+  Serial.println(msg);
+  while (!Serial.available()) {}
+  Serial.println();
+}
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+  while (!Serial) {
+    yield();
+  }
+  waitSerial("Type any character to begin");
+  Serial.print("FreeStack: ");
+  Serial.println(FreeStack());
+}
+//------------------------------------------------------------------------------
+void loop() {
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+  }
+//analogReadAveraging(1);
+//analogReadResolution(12);
+//overclock(); // 3 Msps on Teensy 3.6 - requires high quality card.
+  runTest(A0);
+  waitSerial("Type any character to run test again");
+}

+ 139 - 0
lib/SdFat_NoArduino/examples/TeensyRtcTimestamp/TeensyRtcTimestamp.ino

@@ -0,0 +1,139 @@
+// Test of time-stamp callback with Teensy 3/4.
+// The upload time will be used to set the RTC.
+// You must arrange for syncing the RTC.
+#include "SdFat.h"
+#include <TimeLib.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 3
+/*
+  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
+
+#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
+
+//------------------------------------------------------------------------------
+// Call back for file timestamps.  Only called for file create and sync().
+void dateTime(uint16_t* date, uint16_t* time, uint8_t* ms10) {
+
+  // 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 low time bits in units of 10 ms.
+  *ms10 = second() & 1 ? 100 : 0;
+}
+//------------------------------------------------------------------------------
+time_t getTeensy3Time()
+{
+  return Teensy3Clock.get();
+}
+//------------------------------------------------------------------------------
+void printField(Print* pr, char sep, uint8_t v) {
+  if (sep) {
+    pr->write(sep);
+  }
+  if (v < 10) {
+    pr->write('0');
+  }
+  pr->print(v);
+}
+//------------------------------------------------------------------------------
+void printNow(Print* pr) {
+  pr->print(year());
+  printField(pr, '-', month());
+  printField(pr, '-', day());
+  printField(pr, ' ', hour());
+  printField(pr, ':', minute());
+  printField(pr, ':', second());
+}
+//------------------------------------------------------------------------------
+void setup() {
+  // set the Time library to use Teensy 3.0's RTC to keep time
+  setSyncProvider(getTeensy3Time);
+
+  Serial.begin(9600);
+  while (!Serial) {
+    yield();
+  }
+  Serial.println(F("Type any character to begin"));
+  while (!Serial.available()) {
+    yield();
+  }
+  if (timeStatus()!= timeSet) {
+    Serial.println("Unable to sync with the RTC");
+    return;
+  }
+  Serial.print(F("DateTime::now "));
+  printNow(&Serial);
+  Serial.println();
+
+  // Set callback
+  FsDateTime::setCallback(dateTime);
+
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+  }
+  // Remove old version to set create time.
+  if (sd.exists("RtcTest.txt")) {
+    sd.remove("RtcTest.txt");
+  }
+  if (!file.open("RtcTest.txt", FILE_WRITE)) {
+    Serial.println(F("file.open failed"));
+    return;
+  }
+  // Print current date time to file.
+  file.print(F("Test file at: "));
+  printNow(&file);
+  file.println();
+
+  file.close();
+  // List files in SD root.
+  sd.ls(LS_DATE | LS_SIZE);
+  Serial.println(F("Done"));
+}
+//------------------------------------------------------------------------------
+void loop() {
+}

+ 221 - 0
lib/SdFat_NoArduino/examples/TeensySdioDemo/TeensySdioDemo.ino

@@ -0,0 +1,221 @@
+// Simple performance test for Teensy 3.5/3.6 4.0 SDHC.
+// Demonstrates yield() efficiency for SDIO modes.
+#include "SdFat.h"
+
+// Use built-in SD for SPI modes on Teensy 3.5/3.6.
+// Teensy 4.0 use first SPI port.
+// 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
+
+// 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 3
+
+// 32 KiB buffer.
+const size_t BUF_DIM = 32768;
+
+// 8 MiB file.
+const uint32_t FILE_SIZE = 256UL*BUF_DIM;
+
+#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
+
+uint8_t buf[BUF_DIM];
+
+// buffer as uint32_t
+uint32_t* buf32 = (uint32_t*)buf;
+
+// Total usec in read/write calls.
+uint32_t totalMicros = 0;
+// Time in yield() function.
+uint32_t yieldMicros = 0;
+// Number of yield calls.
+uint32_t yieldCalls = 0;
+// Max busy time for single yield call.
+uint32_t yieldMaxUsec = 0;
+//------------------------------------------------------------------------------
+void clearSerialInput() {
+  uint32_t m = micros();
+  do {
+    if (Serial.read() >= 0) {
+      m = micros();
+    }
+  } while (micros() - m < 10000);
+}
+//------------------------------------------------------------------------------
+void errorHalt(const char* msg) {
+  Serial.print("Error: ");
+  Serial.println(msg);
+  if (sd.sdErrorCode()) {
+    if (sd.sdErrorCode() == SD_CARD_ERROR_ACMD41) {
+      Serial.println("Try power cycling the SD card.");
+    }
+    printSdErrorSymbol(&Serial, sd.sdErrorCode());
+    Serial.print(", ErrorData: 0X");
+    Serial.println(sd.sdErrorData(), HEX);
+  }
+  while (true) {}
+}
+bool ready = false;
+//------------------------------------------------------------------------------
+bool sdBusy() {
+  return ready ? sd.card()->isBusy() : false;
+}
+//------------------------------------------------------------------------------
+// Replace "weak" system yield() function.
+void yield() {
+  // Only count cardBusy time.
+  if (!sdBusy()) {
+    return;
+  }
+  uint32_t m = micros();
+  yieldCalls++;
+  while (sdBusy()) {
+    // Do something here.
+  }
+  m = micros() - m;
+  if (m > yieldMaxUsec) {
+    yieldMaxUsec = m;
+  }
+  yieldMicros += m;
+}
+//------------------------------------------------------------------------------
+void runTest() {
+  // Zero Stats
+  totalMicros = 0;
+  yieldMicros = 0;
+  yieldCalls = 0;
+  yieldMaxUsec = 0;
+  if (!file.open("TeensyDemo.bin", O_RDWR | O_CREAT)) {
+    errorHalt("open failed");
+  }
+  Serial.println("\nsize,write,read");
+  Serial.println("bytes,KB/sec,KB/sec");
+  for (size_t nb = 512; nb <= BUF_DIM; nb *= 2) {
+    uint32_t nRdWr = FILE_SIZE/nb;
+    if (!file.truncate(0)) {
+      errorHalt("truncate failed");
+    }
+
+    Serial.print(nb);
+    Serial.print(',');
+    uint32_t t = micros();
+    for (uint32_t n = 0; n < nRdWr; n++) {
+      // Set start and end of buffer.
+      buf32[0] = n;
+      buf32[nb/4 - 1] = n;
+      if (nb != file.write(buf, nb)) {
+        errorHalt("write failed");
+      }
+    }
+    t = micros() - t;
+    totalMicros += t;
+    Serial.print(1000.0*FILE_SIZE/t);
+    Serial.print(',');
+    file.rewind();
+    t = micros();
+
+    for (uint32_t n = 0; n < nRdWr; n++) {
+      if ((int)nb != file.read(buf, nb)) {
+        errorHalt("read failed");
+      }
+      // crude check of data.
+      if (buf32[0] != n || buf32[nb/4 - 1] != n) {
+        errorHalt("data check");
+      }
+    }
+    t = micros() - t;
+    totalMicros += t;
+    Serial.println(1000.0*FILE_SIZE/t);
+  }
+  file.close();
+  Serial.print("\ntotalMicros  ");
+  Serial.println(totalMicros);
+  Serial.print("yieldMicros  ");
+  Serial.println(yieldMicros);
+  Serial.print("yieldCalls   ");
+  Serial.println(yieldCalls);
+  Serial.print("yieldMaxUsec ");
+  Serial.println(yieldMaxUsec);
+//  Serial.print("kHzSdClk     ");
+//  Serial.println(kHzSdClk());
+  Serial.println("Done");
+}
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+  while (!Serial) {
+  }
+}
+//------------------------------------------------------------------------------
+void loop() {
+  static bool warn = true;
+  if (warn) {
+    warn = false;
+    Serial.println(
+      "SD cards must be power cycled to leave\n"
+      "SPI mode so do SDIO tests first.\n"
+      "\nCycle power on the card if an error occurs.");
+  }
+  clearSerialInput();
+
+  Serial.println(
+    "\nType '1' for FIFO SDIO"
+    "\n     '2' for DMA SDIO"
+    "\n     '3' for Dedicated SPI"
+    "\n     '4' for Shared SPI");
+  while (!Serial.available()) {
+  }
+  char c = Serial.read();
+
+  if (c =='1') {
+    if (!sd.begin(SdioConfig(FIFO_SDIO))) {
+      errorHalt("begin failed");
+    }
+    Serial.println("\nFIFO SDIO mode.");
+  } else if (c == '2') {
+    if (!sd.begin(SdioConfig(DMA_SDIO))) {
+      errorHalt("begin failed");
+    }
+    Serial.println("\nDMA SDIO mode - slow for small transfers.");
+  } else if (c == '3') {
+#if ENABLE_DEDICATED_SPI
+    if (!sd.begin(SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(50)))) {
+      errorHalt("begin failed");
+    }
+    Serial.println("\nDedicated SPI mode.");
+#else  // ENABLE_DEDICATED_SPI
+    Serial.println("ENABLE_DEDICATED_SPI must be non-zero.");
+    return;
+#endif  // ENABLE_DEDICATED_SPI
+  } else if (c == '4') {
+    if (!sd.begin(SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(50)))) {
+      errorHalt("begin failed");
+    }
+    Serial.println("\nShared SPI mode - slow for small transfers.");
+  } else {
+    Serial.println("Invalid input");
+    return;
+  }
+  ready = true;
+  runTest();
+  ready = false;
+}

+ 148 - 0
lib/SdFat_NoArduino/examples/TeensySdioLogger/TeensySdioLogger.ino

@@ -0,0 +1,148 @@
+// Test Teensy SDIO with write busy in a data logger demo.
+//
+// The driver writes to the uSDHC controller's FIFO then returns
+// while the controller writes the data to the SD.  The first sector
+// puts the controller in write mode and takes about 11 usec on a
+// Teensy 4.1. About 5 usec is required to write a sector when the
+// controller is in write mode.
+
+#include "SdFat.h"
+#include "RingBuf.h"
+
+// Use Teensy SDIO
+#define SD_CONFIG  SdioConfig(FIFO_SDIO)
+
+// Interval between points for 25 ksps.
+#define LOG_INTERVAL_USEC 40
+
+// Size to log 10 byte lines at 25 kHz for more than ten minutes.
+#define LOG_FILE_SIZE 10*25000*600  // 150,000,000 bytes.
+
+// Space to hold more than 800 ms of data for 10 byte lines at 25 ksps.
+#define RING_BUF_CAPACITY 400*512
+#define LOG_FILENAME "SdioLogger.csv"
+
+SdFs sd;
+FsFile file;
+
+// RingBuf for File type FsFile.
+RingBuf<FsFile, RING_BUF_CAPACITY> rb;
+
+void logData() {
+  // Initialize the SD.
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+  }
+  // Open or create file - truncate existing file.
+  if (!file.open(LOG_FILENAME, O_RDWR | O_CREAT | O_TRUNC)) {
+    Serial.println("open failed\n");
+    return;
+  }
+  // File must be pre-allocated to avoid huge
+  // delays searching for free clusters.
+  if (!file.preAllocate(LOG_FILE_SIZE)) {
+     Serial.println("preAllocate failed\n");
+     file.close();
+     return;
+  }
+  // initialize the RingBuf.
+  rb.begin(&file);
+  Serial.println("Type any character to stop");
+
+  // Max RingBuf used bytes. Useful to understand RingBuf overrun.
+  size_t maxUsed = 0;
+
+  // Min spare micros in loop.
+  int32_t minSpareMicros = INT32_MAX;
+
+  // Start time.
+  uint32_t logTime = micros();
+  // Log data until Serial input or file full.
+  while (!Serial.available()) {
+    // Amount of data in ringBuf.
+    size_t n = rb.bytesUsed();
+    if ((n + file.curPosition()) > (LOG_FILE_SIZE - 20)) {
+      Serial.println("File full - quitting.");
+      break;
+    }
+    if (n > maxUsed) {
+      maxUsed = n;
+    }
+    if (n >= 512 && !file.isBusy()) {
+      // Not busy only allows one sector before possible busy wait.
+      // Write one sector from RingBuf to file.
+      if (512 != rb.writeOut(512)) {
+        Serial.println("writeOut failed");
+        break;
+      }
+    }
+    // Time for next point.
+    logTime += LOG_INTERVAL_USEC;
+    int32_t spareMicros = logTime - micros();
+    if (spareMicros < minSpareMicros) {
+      minSpareMicros = spareMicros;
+    }
+    if (spareMicros <= 0) {
+      Serial.print("Rate too fast ");
+      Serial.println(spareMicros);
+      break;
+    }
+    // Wait until time to log data.
+    while (micros() < logTime) {}
+
+    // Read ADC0 - about 17 usec on Teensy 4, Teensy 3.6 is faster.
+    uint16_t adc = analogRead(0);
+    // Print spareMicros into the RingBuf as test data.
+    rb.print(spareMicros);
+    rb.write(',');
+    // Print adc into RingBuf.
+    rb.println(adc);
+    if (rb.getWriteError()) {
+      // Error caused by too few free bytes in RingBuf.
+      Serial.println("WriteError");
+      break;
+    }
+  }
+  // Write any RingBuf data to file.
+  rb.sync();
+  file.truncate();
+  file.rewind();
+  // Print first twenty lines of file.
+  Serial.println("spareMicros,ADC0");
+  for (uint8_t n = 0; n < 20 && file.available();) {
+    int c = file.read();
+    if (c < 0) {
+      break;
+    }
+    Serial.write(c);
+    if (c == '\n') n++;
+  }
+  Serial.print("fileSize: ");
+  Serial.println((uint32_t)file.fileSize());
+  Serial.print("maxBytesUsed: ");
+  Serial.println(maxUsed);
+  Serial.print("minSpareMicros: ");
+  Serial.println(minSpareMicros);
+  file.close();
+}
+void clearSerialInput() {
+  for (uint32_t m = micros(); micros() - m < 10000;) {
+    if (Serial.read() >= 0) {
+      m = micros();
+    }
+  }
+}
+void setup() {
+  Serial.begin(9600);
+  while (!Serial) {}
+  // Go faster or log more channels.  ADC quality will suffer.
+  // analogReadAveraging(1);
+}
+
+void loop() {
+  clearSerialInput();
+  Serial.println("Type any character to start");
+  while (!Serial.available()) {};
+  clearSerialInput();
+  logData();
+}

+ 98 - 0
lib/SdFat_NoArduino/examples/UnicodeFilenames/UnicodeFilenames.ino

@@ -0,0 +1,98 @@
+// Simple test of Unicode filename.
+// Unicode is supported as UTF-8 encoded strings.
+#include "SdFat.h"
+
+// USE_UTF8_LONG_NAMES must be non-zero in SdFat/src/SdFatCongfig.h
+#if USE_UTF8_LONG_NAMES
+
+#define UTF8_FOLDER u8"😀"
+const char* names[] = {u8"россиянин", u8"très élégant", u8"狗.txt", nullptr};
+
+// Remove files if non-zero.
+#define REMOVE_UTF8_FILES 1
+
+// 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
+
+// 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 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
+
+#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
+
+void setup() {
+  Serial.begin(9600);
+  while (!Serial) {
+    yield();
+  }
+  Serial.println("Type any character to begin");
+  while (!Serial.available()) {
+    yield();
+  }
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+  }
+  if (!sd.exists(UTF8_FOLDER)) {
+    if (!sd.mkdir(UTF8_FOLDER)) {
+      Serial.println("sd.mkdir failed");
+      return;
+    }
+  }
+  if (!sd.chdir(UTF8_FOLDER)) {
+    Serial.println("sd.chdir failed");
+    return;
+  }
+  for (uint8_t i = 0; names[i]; i++) {
+    if (!file.open(names[i], O_WRONLY | O_CREAT)) {
+      Serial.println("file.open failed");
+      return;
+    }
+    file.println(names[i]);
+    file.close();
+  }
+  Serial.println("ls:");
+  sd.ls("/", LS_SIZE | LS_R);
+#if REMOVE_UTF8_FILES  // For debug test of remove and rmdir.
+  for (uint8_t i = 0; names[i]; i++) {
+    sd.remove(names[i]);
+  }
+  sd.chdir();
+  sd.rmdir(UTF8_FOLDER);
+  Serial.println("After remove and rmdir");
+  sd.ls(LS_SIZE | LS_R);
+#endif  // REMOVE_UTF8_FILES
+  Serial.println("Done!");
+}
+void loop() {
+}
+#else  // USE_UTF8_LONG_NAMES
+#error USE_UTF8_LONG_NAMES must be non-zero in SdFat/src/SdFatCongfig.h
+#endif  // USE_UTF8_LONG_NAMES

+ 47 - 0
lib/SdFat_NoArduino/examples/UserChipSelectFunction/UserChipSelectFunction.ino

@@ -0,0 +1,47 @@
+// An example of an external chip select functions.
+// Useful for port expanders or replacement of the standard GPIO functions.
+//
+#include "SdFat.h"
+
+// SD_CHIP_SELECT_MODE must be set to one or two in SdFat/SdFatConfig.h.
+// A value of one allows optional replacement and two requires replacement.
+#if SD_CHIP_SELECT_MODE == 1 || SD_CHIP_SELECT_MODE == 2
+
+// SD chip select pin.
+#define SD_CS_PIN SS
+
+#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(50))
+
+SdFat sd;
+
+// Stats to verify function calls.
+uint32_t initCalls = 0;
+uint32_t writeCalls = 0;
+//------------------------------------------------------------------------------
+// Modify these functions for your port expander or custom GPIO library.
+void sdCsInit(SdCsPin_t pin) {
+  initCalls++;
+  pinMode(pin, OUTPUT);
+}
+void sdCsWrite(SdCsPin_t pin, bool level) {
+  writeCalls++;
+  digitalWrite(pin, level);
+}
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+  }
+  sd.ls(&Serial, LS_SIZE);
+
+  Serial.print(F("sdCsInit calls: "));
+  Serial.println(initCalls);
+  Serial.print(F("sdCsWrite calls: "));
+  Serial.println(writeCalls);
+}
+//------------------------------------------------------------------------------
+void loop() {}
+#else  // SD_CHIP_SELECT_MODE == 1 || SD_CHIP_SELECT_MODE == 2
+#error SD_CHIP_SELECT_MODE must be one or two in SdFat/SdFatConfig.h
+#endif  // SD_CHIP_SELECT_MODE == 1 || SD_CHIP_SELECT_MODE == 2

+ 81 - 0
lib/SdFat_NoArduino/examples/UserSPIDriver/UserSPIDriver.ino

@@ -0,0 +1,81 @@
+// An example of an external SPI driver.
+//
+#include "SdFat.h"
+#include "SPI.h"  // Only required if you use features in the SPI library.
+
+#if SPI_DRIVER_SELECT == 3  // Must be set in SdFat/SdFatConfig.h
+
+// SD chip select pin.
+#define SD_CS_PIN SS
+
+// This is a simple driver based on the the standard SPI.h library.
+// You can write a driver entirely independent of SPI.h.
+// It can be optimized for your board or a different SPI port can be used.
+// The driver must be derived from SdSpiBaseClass.
+// See: SdFat/src/SpiDriver/SdSpiBaseClass.h
+class MySpiClass : public SdSpiBaseClass {
+ public:
+  // Activate SPI hardware with correct speed and mode.
+  void activate() {
+    SPI.beginTransaction(m_spiSettings);
+  }
+  // Initialize the SPI bus.
+  void begin(SdSpiConfig config) {
+    (void)config;
+    SPI.begin();
+  }
+  // Deactivate SPI hardware.
+  void deactivate() {
+    SPI.endTransaction();
+  }
+  // Receive a byte.
+  uint8_t receive() {
+    return SPI.transfer(0XFF);
+  }
+  // Receive multiple bytes.
+  // Replace this function if your board has multiple byte receive.
+  uint8_t receive(uint8_t* buf, size_t count) {
+    for (size_t i = 0; i < count; i++) {
+      buf[i] = SPI.transfer(0XFF);
+    }
+    return 0;
+  }
+  // Send a byte.
+  void send(uint8_t data) {
+    SPI.transfer(data);
+  }
+  // Send multiple bytes.
+  // Replace this function if your board has multiple byte send.
+  void send(const uint8_t* buf, size_t count) {
+    for (size_t i = 0; i < count; i++) {
+      SPI.transfer(buf[i]);
+    }
+  }
+  // Save SPISettings for new max SCK frequency
+  void setSckSpeed(uint32_t maxSck) {
+    m_spiSettings = SPISettings(maxSck, MSBFIRST, SPI_MODE0);
+  }
+
+ private:
+  SPISettings m_spiSettings;
+} mySpi;
+#if ENABLE_DEDICATED_SPI
+#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(50), &mySpi)
+#else  // ENABLE_DEDICATED_SPI
+#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(50), &mySpi)
+#endif  // ENABLE_DEDICATED_SPI
+SdFat sd;
+
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+  if (!sd.begin(SD_CONFIG)) {
+    sd.initErrorHalt(&Serial);
+  }
+  sd.ls(&Serial, LS_SIZE);
+}
+//------------------------------------------------------------------------------
+void loop() {}
+#else  // SPI_DRIVER_SELECT
+#error SPI_DRIVER_SELECT must be three in SdFat/SdFatConfig.h
+#endif  // SPI_DRIVER_SELECT

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

@@ -0,0 +1,276 @@
+/*
+ * 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 << 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("\nVersion: ");
+  cout << int(cid.prv_n) << '.' << int(cid.prv_m) << endl;
+  cout << F("Serial number: ") << hex << cid.psn << dec << endl;
+  cout << F("Manufacturing date: ");
+  cout << int(cid.mdt_month) << '/';
+  cout << (2000 + cid.mdt_year_low + 10 * cid.mdt_year_high) << 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();
+}

+ 197 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/AnalogLogger/AnalogLogger.ino

@@ -0,0 +1,197 @@
+// A simple data logger for the Arduino analog pins with optional DS1307
+// uses RTClib from https://github.com/adafruit/RTClib
+#include <SPI.h>
+#include "SdFat.h"
+#include "sdios.h"
+#include "FreeStack.h"
+
+#define SD_CHIP_SELECT  SS  // SD chip select pin
+#define USE_DS1307       0  // set nonzero to use DS1307 RTC
+#define LOG_INTERVAL  1000  // mills between entries
+#define SENSOR_COUNT     3  // number of analog pins to log
+#define ECHO_TO_SERIAL   1  // echo data to serial port if nonzero
+#define WAIT_TO_START    1  // Wait for serial input in setup()
+#define ADC_DELAY       10  // ADC delay for high impedence sensors
+
+// file system object
+SdFat sd;
+
+// text file for logging
+ofstream logfile;
+
+// Serial print stream
+ArduinoOutStream cout(Serial);
+
+// buffer to format data - makes it eaiser to echo to Serial
+char buf[80];
+//------------------------------------------------------------------------------
+#if SENSOR_COUNT > 6
+#error SENSOR_COUNT too large
+#endif  // SENSOR_COUNT
+//------------------------------------------------------------------------------
+// store error strings in flash to save RAM
+#define error(s) sd.errorHalt(F(s))
+//------------------------------------------------------------------------------
+#if USE_DS1307
+// use RTClib from Adafruit
+// https://github.com/adafruit/RTClib
+
+// The Arduino IDE has a bug that causes Wire and RTClib to be loaded even
+// if USE_DS1307 is false.
+
+#error remove this line and uncomment the next two lines.
+//#include <Wire.h>
+//#include <RTClib.h>
+RTC_DS1307 RTC;  // define the Real Time Clock object
+//------------------------------------------------------------------------------
+// call back for file timestamps
+void dateTime(uint16_t* date, uint16_t* time) {
+  DateTime now = RTC.now();
+
+  // return date using FAT_DATE macro to format fields
+  *date = FAT_DATE(now.year(), now.month(), now.day());
+
+  // return time using FAT_TIME macro to format fields
+  *time = FAT_TIME(now.hour(), now.minute(), now.second());
+}
+//------------------------------------------------------------------------------
+// format date/time
+ostream& operator << (ostream& os, DateTime& dt) {
+  os << dt.year() << '/' << int(dt.month()) << '/' << int(dt.day()) << ',';
+  os << int(dt.hour()) << ':' << setfill('0') << setw(2) << int(dt.minute());
+  os << ':' << setw(2) << int(dt.second()) << setfill(' ');
+  return os;
+}
+#endif  // USE_DS1307
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial.
+  while (!Serial) {
+    yield();
+  }
+  // F() stores strings in flash to save RAM
+  cout << endl << F("FreeStack: ") << FreeStack() << endl;
+
+#if WAIT_TO_START
+  cout << F("Type any character to start\n");
+  while (!Serial.available()) {
+    yield();
+  }
+  // Discard input.
+  do {
+    delay(10);
+  } while(Serial.available() && Serial.read() >= 0);
+#endif  // WAIT_TO_START
+
+#if USE_DS1307
+  // connect to RTC
+  Wire.begin();
+  if (!RTC.begin()) {
+    error("RTC failed");
+  }
+
+  // set date time callback function
+  SdFile::dateTimeCallback(dateTime);
+  DateTime now = RTC.now();
+  cout  << now << endl;
+#endif  // USE_DS1307
+
+  // Initialize at the highest speed supported by the board that is
+  // not over 50 MHz. Try a lower speed if SPI errors occur.
+  if (!sd.begin(SD_CHIP_SELECT, SD_SCK_MHZ(50))) {
+    sd.initErrorHalt();
+  }
+
+  // create a new file in root, the current working directory
+  char name[] = "logger00.csv";
+
+  for (uint8_t i = 0; i < 100; i++) {
+    name[6] = i/10 + '0';
+    name[7] = i%10 + '0';
+    if (sd.exists(name)) {
+      continue;
+    }
+    logfile.open(name);
+    break;
+  }
+  if (!logfile.is_open()) {
+    error("file.open");
+  }
+
+  cout << F("Logging to: ") << name << endl;
+  cout << F("Type any character to stop\n\n");
+
+  // format header in buffer
+  obufstream bout(buf, sizeof(buf));
+
+  bout << F("millis");
+
+#if USE_DS1307
+  bout << F(",date,time");
+#endif  // USE_DS1307
+
+  for (uint8_t i = 0; i < SENSOR_COUNT; i++) {
+    bout << F(",sens") << int(i);
+  }
+  logfile << buf << endl;
+
+#if ECHO_TO_SERIAL
+  cout << buf << endl;
+#endif  // ECHO_TO_SERIAL
+}
+//------------------------------------------------------------------------------
+void loop() {
+  uint32_t m;
+
+  // wait for time to be a multiple of interval
+  do {
+    m = millis();
+  } while (m % LOG_INTERVAL);
+
+  // use buffer stream to format line
+  obufstream bout(buf, sizeof(buf));
+
+  // start with time in millis
+  bout << m;
+
+#if USE_DS1307
+  DateTime now = RTC.now();
+  bout << ',' << now;
+#endif  // USE_DS1307
+
+  // read analog pins and format data
+  for (uint8_t ia = 0; ia < SENSOR_COUNT; ia++) {
+#if ADC_DELAY
+    analogRead(ia);
+    delay(ADC_DELAY);
+#endif  // ADC_DELAY
+    bout << ',' << analogRead(ia);
+  }
+  bout << endl;
+
+  // log data and flush to SD
+  logfile << buf << flush;
+
+  // check for error
+  if (!logfile) {
+    error("write data failed");
+  }
+
+#if ECHO_TO_SERIAL
+  cout << buf;
+#endif  // ECHO_TO_SERIAL
+
+  // don't log two points in the same millis
+  if (m == millis()) {
+    delay(1);
+  }
+
+  if (!Serial.available()) {
+    return;
+  }
+  logfile.close();
+  cout << F("Done!");
+  while (true) {}
+}

+ 46 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/BaseExtCaseTest/BaseExtCaseTest.ino

@@ -0,0 +1,46 @@
+/*
+ * Program to test Short File Name character case flags.
+ */
+#include <SPI.h>
+#include "SdFat.h"
+
+const uint8_t chipSelect = SS;
+
+SdFat sd;
+
+SdFile file;
+const char* name[] = {
+  "low.low", "low.Mix", "low.UP",
+  "Mix.low", "Mix.Mix", "Mix.UP",
+  "UP.low",  "UP.Mix",  "UP.UP"
+};
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  Serial.println("type any character to start");
+  while (!Serial.available()) {
+    yield();
+  }
+  if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
+    Serial.println("begin failed");
+    return;
+  }
+  for (uint8_t i = 0; i < 9; i++) {
+    sd.remove(name[i]);
+    if (!file.open(name[i], O_RDWR | O_CREAT | O_EXCL)) {
+      sd.errorHalt(name[i]);
+    }
+    file.println(name[i]);
+
+    file.close();
+  }
+  sd.ls(LS_DATE|LS_SIZE);
+  Serial.println("Done");
+}
+//------------------------------------------------------------------------------
+void loop() {}

+ 20 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/HelloWorld/HelloWorld.ino

@@ -0,0 +1,20 @@
+#include <SPI.h>
+#include "SdFat.h"
+#include "sdios.h"
+
+//  create a serial output stream
+ArduinoOutStream cout(Serial);
+
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  delay(2000);
+
+  cout << "Hello, World!\n";
+}
+
+void loop() {}

+ 29 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/MiniSerial/MiniSerial.ino

@@ -0,0 +1,29 @@
+// This example illustrates use of SdFat's
+// minimal unbuffered AVR Serial support.
+//
+// This is useful for debug and saves RAM
+// Will not work on Due, Leonardo, or Teensy
+
+#include <SPI.h>
+#include "SdFat.h"
+#include "FreeStack.h"
+#ifdef UDR0  // Must be AVR with serial port zero.
+#include "MinimumSerial.h"
+
+MinimumSerial MiniSerial;
+
+void setup() {
+  MiniSerial.begin(9600);
+  MiniSerial.println(FreeStack());
+}
+void loop() {
+  int c;
+  MiniSerial.println(F("Type any Character"));
+  while ((c = MiniSerial.read()) < 0) {}
+  MiniSerial.print(F("Read: "));
+  MiniSerial.println((char)c);
+  while (MiniSerial.read() >= 0) {}
+}
+#else  // UDR0
+#error no AVR serial port 0
+#endif  // UDR0

+ 125 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/PrintBenchmarkSD/PrintBenchmarkSD.ino

@@ -0,0 +1,125 @@
+/*
+ * This program is a simple Print benchmark.
+ */
+#include <SPI.h>
+#include <SD.h>
+
+// SD chip select pin
+const uint8_t chipSelect = SS;
+
+// number of lines to print
+const uint16_t N_PRINT = 20000;
+
+
+// test file
+File file;
+
+//------------------------------------------------------------------------------
+void error(const char* s) {
+  Serial.println(s);
+  while (1) {
+    yield();
+  }
+}
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+}
+//------------------------------------------------------------------------------
+void loop() {
+  uint32_t maxLatency;
+  uint32_t minLatency;
+  uint32_t totalLatency;
+
+  // Read any existing Serial data.
+  do {
+    delay(10);
+  } while (Serial.available() && Serial.read() >= 0);
+
+  // F() stores strings in flash to save RAM
+  Serial.println(F("Type any character to start"));
+  while (!Serial.available()) {
+    yield();
+  }
+
+  // initialize the SD card
+  if (!SD.begin(chipSelect)) {
+    error("begin");
+  }
+
+  Serial.println(F("Starting print test.  Please wait.\n"));
+
+  // do write test
+  for (int test = 0; test < 2; test++) {
+    file = SD.open("bench.txt", FILE_WRITE);
+
+    if (!file) {
+      error("open failed");
+    }
+    switch(test) {
+    case 0:
+      Serial.println(F("Test of println(uint16_t)"));
+      break;
+
+    case 1:
+      Serial.println(F("Test of println(double)"));
+      break;
+    }
+    maxLatency = 0;
+    minLatency = 999999;
+    totalLatency = 0;
+    uint32_t t = millis();
+    for (uint16_t i = 0; i < N_PRINT; i++) {
+      uint32_t m = micros();
+
+      switch(test) {
+      case 0:
+        file.println(i);
+        break;
+
+      case 1:
+        file.println((double)0.01*i);
+        break;
+      }
+
+      if (file.getWriteError()) {
+        error("write failed");
+      }
+      m = micros() - m;
+      if (maxLatency < m) {
+        maxLatency = m;
+      }
+      if (minLatency > m) {
+        minLatency = m;
+      }
+      totalLatency += m;
+    }
+    file.flush();
+    t = millis() - t;
+    double s = file.size();
+    Serial.print(F("Time "));
+    Serial.print(0.001*t);
+    Serial.println(F(" sec"));
+    Serial.print(F("File size "));
+    Serial.print(0.001*s);
+    Serial.print(F(" KB\n"));
+    Serial.print(F("Write "));
+    Serial.print(s/t);
+    Serial.print(F(" KB/sec\n"));
+    Serial.print(F("Maximum latency: "));
+    Serial.print(maxLatency);
+    Serial.print(F(" usec, Minimum Latency: "));
+    Serial.print(minLatency);
+    Serial.print(F(" usec, Avg Latency: "));
+    Serial.print(totalLatency/N_PRINT);
+    Serial.println(F(" usec\n"));
+    SD.remove("bench.txt");
+  }
+  file.close();
+  Serial.println(F("Done!\n"));
+}

+ 30 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/SD_Size/SD_Size.ino

@@ -0,0 +1,30 @@
+/*
+ * Program to compare size of Arduino SD library with SdFat.
+ * See SdFatSize.ino for SdFat program.
+ */
+#include <SPI.h>
+#include <SD.h>
+
+File file;
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+
+  if (!SD.begin()) {
+    Serial.println("begin failed");
+    return;
+  }
+  file = SD.open("TEST_SD.TXT", FILE_WRITE);
+
+  file.println("Hello");
+
+  file.close();
+  Serial.println("Done");
+}
+//------------------------------------------------------------------------------
+void loop() {}

+ 33 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/SdFatSize/SdFatSize.ino

@@ -0,0 +1,33 @@
+/*
+ * Program to compare size of SdFat with Arduino SD library.
+ * See SD_Size.ino for Arduino SD program.
+ *
+ */
+#include <SPI.h>
+#include "SdFat.h"
+
+SdFat sd;
+
+SdFile file;
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+
+  if (!sd.begin()) {
+    Serial.println("begin failed");
+    return;
+  }
+  file.open("SizeTest.txt", O_RDWR | O_CREAT | O_AT_END);
+
+  file.println("Hello");
+
+  file.close();
+  Serial.println("Done");
+}
+//------------------------------------------------------------------------------
+void loop() {}

+ 44 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/StreamParseInt/StreamParseInt.ino

@@ -0,0 +1,44 @@
+// Simple demo of the Stream parsInt() member function.
+#include <SPI.h>
+// The next two lines replace #include <SD.h>.
+#include "SdFat.h"
+SdFat SD;
+
+// SD card chip select pin - Modify the value of csPin for your SD module.
+const uint8_t csPin = SS;
+
+File file;
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+  // Wait for USB Serial.
+  while(!Serial) {
+    yield();
+  }
+  Serial.println(F("Type any character to start"));
+  while (!Serial.available()) {
+    yield();
+  }
+  // Initialize the SD.
+  if (!SD.begin(csPin)) {
+    Serial.println(F("begin error"));
+    return;
+  }
+  // Create and open the file.  Use flag to truncate an existing file.
+  file = SD.open("stream.txt", O_RDWR|O_CREAT|O_TRUNC);
+  if (!file) {
+    Serial.println(F("open error"));
+    return;
+  }
+  // Write a test number to the file.
+  file.println("12345");
+
+  // Rewind the file and read the number with parseInt().
+  file.seek(0);
+  int i = file.parseInt();
+  Serial.print(F("parseInt: "));
+  Serial.println(i);
+  file.close();
+}
+
+void loop() {}

+ 77 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/append/append.ino

@@ -0,0 +1,77 @@
+/*
+ * Append Example
+ *
+ * This program shows how to use open for append.
+ * The program will append 100 line each time it opens the file.
+ * The program will open and close the file 100 times.
+ */
+#include <SPI.h>
+#include "SdFat.h"
+#include "sdios.h"
+
+// SD chip select pin
+const uint8_t chipSelect = SS;
+
+// file system object
+SdFat sd;
+
+// create Serial stream
+ArduinoOutStream cout(Serial);
+
+// store error strings in flash to save RAM
+#define error(s) sd.errorHalt(F(s))
+//------------------------------------------------------------------------------
+void setup() {
+  // filename for this example
+  char name[] = "append.txt";
+
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  // F() stores strings in flash to save RAM
+  cout << endl << F("Type any character to start\n");
+  while (!Serial.available()) {
+    yield();
+  }
+
+  // Initialize at the highest speed supported by the board that is
+  // not over 50 MHz. Try a lower speed if SPI errors occur.
+  if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
+    sd.initErrorHalt();
+  }
+
+  cout << F("Appending to: ") << name;
+
+  for (uint8_t i = 0; i < 100; i++) {
+    // open stream for append
+    ofstream sdout(name, ios::out | ios::app);
+    if (!sdout) {
+      error("open failed");
+    }
+
+    // append 100 lines to the file
+    for (uint8_t j = 0; j < 100; j++) {
+      // use int() so byte will print as decimal number
+      sdout << "line " << int(j) << " of pass " << int(i);
+      sdout << " millis = " << millis() << endl;
+    }
+    // close the stream
+    sdout.close();
+
+    if (!sdout) {
+      error("append data failed");
+    }
+
+    // output progress indicator
+    if (i % 25 == 0) {
+      cout << endl;
+    }
+    cout << '.';
+  }
+  cout << endl << "Done" << endl;
+}
+//------------------------------------------------------------------------------
+void loop() {}

+ 82 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/average/average.ino

@@ -0,0 +1,82 @@
+/*
+ * Calculate the sum and average of a list of floating point numbers
+ */
+#include <SPI.h>
+#include "SdFat.h"
+#include "sdios.h"
+
+// SD chip select pin
+const uint8_t chipSelect = SS;
+
+// object for the SD file system
+SdFat sd;
+
+// define a serial output stream
+ArduinoOutStream cout(Serial);
+//------------------------------------------------------------------------------
+void writeTestFile() {
+  // open the output file
+  ofstream sdout("AvgTest.txt");
+
+  // write a series of float numbers
+  for (int16_t i = -1001; i < 2000; i += 13) {
+    sdout << 0.1 * i << endl;
+  }
+  if (!sdout) {
+    sd.errorHalt("sdout failed");
+  }
+
+  sdout.close();
+}
+//------------------------------------------------------------------------------
+void calcAverage() {
+  uint16_t n = 0;  // count of input numbers
+  double num;      // current input number
+  double sum = 0;  // sum of input numbers
+
+  // open the input file
+  ifstream sdin("AvgTest.txt");
+
+  // check for an open failure
+  if (!sdin) {
+    sd.errorHalt("sdin failed");
+  }
+
+  // read and sum the numbers
+  while (sdin >> num) {
+    n++;
+    sum += num;
+  }
+
+  // print the results
+  cout << "sum of " << n << " numbers = " << sum << endl;
+  cout << "average = " << sum/n << endl;
+}
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  // F() stores strings in flash to save RAM
+  cout << F("Type any character to start\n");
+  while (!Serial.available()) {
+    yield();
+  }
+
+  // Initialize at the highest speed supported by the board that is
+  // not over 50 MHz. Try a lower speed if SPI errors occur.
+  if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
+    sd.initErrorHalt();
+  }
+
+  // write the test file
+  writeTestFile();
+
+  // read the test file and calculate the average
+  calcAverage();
+}
+//------------------------------------------------------------------------------
+void loop() {}

+ 149 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/benchSD/benchSD.ino

@@ -0,0 +1,149 @@
+/*
+ * This program is a simple binary write/read benchmark
+ * for the standard Arduino SD.h library.
+ */
+#include <SPI.h>
+#include <SD.h>
+
+// SD chip select pin
+const uint8_t chipSelect = SS;
+
+#define FILE_SIZE_MB 5
+#define FILE_SIZE (1000000UL*FILE_SIZE_MB)
+#define BUF_SIZE 100
+
+uint8_t buf[BUF_SIZE];
+
+// test file
+File file;
+
+//------------------------------------------------------------------------------
+void error(const char* s) {
+  Serial.println(s);
+  while (1) {
+    yield();
+  }
+}
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+}
+//------------------------------------------------------------------------------
+void loop() {
+  uint32_t maxLatency;
+  uint32_t minLatency;
+  uint32_t totalLatency;
+
+  // Discard any input.
+  do {
+    delay(10);
+  } while (Serial.available() && Serial.read() >= 0);
+
+  // F() stores strings in flash to save RAM
+  Serial.println(F("Type any character to start"));
+
+  while (!Serial.available()) {
+    yield();
+  }
+  if (!SD.begin(chipSelect)) {
+    error("begin");
+  }
+
+  // open or create file - truncate existing file.
+  file = SD.open("Bench.dat", O_RDWR | O_TRUNC | O_CREAT);
+  if (!file) {
+    error("open failed");
+  }
+
+  // fill buf with known data
+  for (size_t_t i = 0; i < (BUF_SIZE-2); i++) {
+    buf[i] = 'A' + (i % 26);
+  }
+  buf[BUF_SIZE-2] = '\r';
+  buf[BUF_SIZE-1] = '\n';
+
+  Serial.print(F("File size "));
+  Serial.print(FILE_SIZE_MB);
+  Serial.println(F("MB"));
+  Serial.print(F("Buffer size "));
+  Serial.print(BUF_SIZE);
+  Serial.println(F(" bytes"));
+  Serial.println(F("Starting write test.  Please wait up to a minute"));
+
+  // do write test
+  uint32_t n = FILE_SIZE/sizeof(buf);
+  maxLatency = 0;
+  minLatency = 999999;
+  totalLatency = 0;
+  uint32_t t = millis();
+  for (uint32_t i = 0; i < n; i++) {
+    uint32_t m = micros();
+    if (file.write(buf, sizeof(buf)) != sizeof(buf)) {
+      error("write failed");
+    }
+    m = micros() - m;
+    if (maxLatency < m) {
+      maxLatency = m;
+    }
+    if (minLatency > m) {
+      minLatency = m;
+    }
+    totalLatency += m;
+  }
+  file.flush();
+  t = millis() - t;
+  double s = file.size();
+  Serial.print(F("Write "));
+  Serial.print(s/t);
+  Serial.print(F(" KB/sec\n"));
+  Serial.print(F("Maximum latency: "));
+  Serial.print(maxLatency);
+  Serial.print(F(" usec, Minimum Latency: "));
+  Serial.print(minLatency);
+  Serial.print(F(" usec, Avg Latency: "));
+  Serial.print(totalLatency/n);
+  Serial.print(F(" usec\n\n"));
+  Serial.println(F("Starting read test.  Please wait up to a minute"));
+  // do read test
+  file.seek(0);
+  maxLatency = 0;
+  minLatency = 99999;
+  totalLatency = 0;
+  t = millis();
+  for (uint32_t i = 0; i < n; i++) {
+    buf[BUF_SIZE-1] = 0;
+    uint32_t m = micros();
+    if (file.read(buf, sizeof(buf)) != sizeof(buf)) {
+      error("read failed");
+    }
+    m = micros() - m;
+    if (maxLatency < m) {
+      maxLatency = m;
+    }
+    if (minLatency > m) {
+      minLatency = m;
+    }
+    totalLatency += m;
+    if (buf[BUF_SIZE-1] != '\n') {
+      error("data check");
+    }
+  }
+  t = millis() - t;
+  Serial.print(F("Read "));
+  Serial.print(s/t);
+  Serial.print(F(" KB/sec\n"));
+  Serial.print(F("Maximum latency: "));
+  Serial.print(maxLatency);
+  Serial.print(F(" usec, Minimum Latency: "));
+  Serial.print(minLatency);
+  Serial.print(F(" usec, Avg Latency: "));
+  Serial.print(totalLatency/n);
+  Serial.print(F(" usec\n\n"));
+  Serial.print(F("Done\n\n"));
+  file.close();
+}

+ 39 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/bufstream/bufstream.ino

@@ -0,0 +1,39 @@
+/*
+ * Use of ibufsteam to parse a line and obufstream to format a line
+ */
+#include <SPI.h>
+#include "SdFat.h"
+#include "sdios.h"
+
+// create a serial output stream
+ArduinoOutStream cout(Serial);
+//------------------------------------------------------------------------------
+void setup() {
+  char buf[20];   // buffer for formatted line
+  int i, j, k;    // values from parsed line
+
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  delay(2000);
+
+  // initialize input string
+  ibufstream bin("123 456 789");
+
+  // parse the string "123 456 789"
+  bin >> i >> j >> k;
+
+  // initialize output buffer
+  obufstream bout(buf, sizeof(buf));
+
+  // format the output string
+  bout << k << ',' << j << ',' << i << endl;
+
+  // write the string to serial
+  cout << buf;
+}
+
+void loop() {}

+ 39 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/cin_cout/cin_cout.ino

@@ -0,0 +1,39 @@
+/*
+ * Demo of ArduinoInStream and ArduinoOutStream
+ */
+#include <SPI.h>
+#include "SdFat.h"
+#include "sdios.h"
+
+// create serial output stream
+ArduinoOutStream cout(Serial);
+
+// input line buffer
+char cinBuf[40];
+
+// create serial input stream
+ArduinoInStream cin(Serial, cinBuf, sizeof(cinBuf));
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+}
+//------------------------------------------------------------------------------
+void loop() {
+  int32_t n = 0;
+
+  cout << "\nenter an integer\n";
+
+  cin.readline();
+
+  if (cin >> n) {
+    cout << "The number is: " << n;
+  } else {
+    // will fail if no digits or not in range [-2147483648, 2147483647]
+    cout << "Invalid input: " << cinBuf;
+  }
+  cout << endl;
+}

+ 62 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/eventlog/eventlog.ino

@@ -0,0 +1,62 @@
+/*
+ * Append a line to a file - demo of pathnames and streams
+ */
+#include <SPI.h>
+#include "SdFat.h"
+#include "sdios.h"
+// SD chip select pin
+const uint8_t chipSelect = SS;
+
+// file system object
+SdFat sd;
+
+// define a serial output stream
+ArduinoOutStream cout(Serial);
+//------------------------------------------------------------------------------
+/*
+ * Append a line to logfile.txt
+ */
+void logEvent(const char *msg) {
+  // create dir if needed
+  sd.mkdir("logs/2014/Jan");
+
+  // create or open a file for append
+  ofstream sdlog("logs/2014/Jan/logfile.txt", ios::out | ios::app);
+
+  // append a line to the file
+  sdlog << msg << endl;
+
+  // check for errors
+  if (!sdlog) {
+    sd.errorHalt("append failed");
+  }
+
+  sdlog.close();
+}
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  // F() stores strings in flash to save RAM
+  cout << F("Type any character to start\n");
+  while (!Serial.available()) {
+    yield();
+  }
+  delay(400);  // catch Due reset problem
+
+  // Initialize at the highest speed supported by the board that is
+  // not over 50 MHz. Try a lower speed if SPI errors occur.
+  if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
+    sd.initErrorHalt();
+  }
+
+  // append a line to the logfile
+  logEvent("Another line for the logfile");
+
+  cout << F("Done - check /logs/2014/Jan/logfile.txt on the SD") << endl;
+}
+//------------------------------------------------------------------------------
+void loop() {}

+ 111 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/fgetsRewrite/fgetsRewrite.ino

@@ -0,0 +1,111 @@
+// Demo of rewriting a line read by fgets
+#include <SPI.h>
+#include "SdFat.h"
+#include "sdios.h"
+
+// SD card chip select pin
+const uint8_t chipSelect = SS;
+
+// file system
+SdFat sd;
+
+// print stream
+ArduinoOutStream cout(Serial);
+//------------------------------------------------------------------------------
+// store error strings in flash memory
+#define error(s) sd.errorHalt(F(s))
+//------------------------------------------------------------------------------
+void demoFgets() {
+  char line[25];
+  int c;
+  uint32_t pos;
+
+  // open test file
+  SdFile rdfile("fgets.txt", O_RDWR);
+
+  // check for open error
+  if (!rdfile.isOpen()) {
+    error("demoFgets");
+  }
+
+  // list file
+  cout << F("-----Before Rewrite\r\n");
+  while ((c = rdfile.read()) >= 0) {
+    Serial.write(c);
+  }
+
+  rdfile.rewind();
+  // read lines from the file to get position
+  while (1) {
+    pos = rdfile.curPosition();
+    if (rdfile.fgets(line, sizeof(line)) < 0) {
+      error("Line not found");
+    }
+    // find line that contains "Line C"
+    if (strstr(line, "Line C")) {
+      break;
+    }
+  }
+
+  // rewrite line with 'C'
+  if (!rdfile.seekSet(pos)) {
+    error("seekSet");
+  }
+  rdfile.println("Line R");
+  rdfile.rewind();
+
+  // list file
+  cout << F("\r\n-----After Rewrite\r\n");
+  while ((c = rdfile.read()) >= 0) {
+    Serial.write(c);
+  }
+
+  // close so rewrite is not lost
+  rdfile.close();
+}
+//------------------------------------------------------------------------------
+void makeTestFile() {
+  // create or open test file
+  SdFile wrfile("fgets.txt", O_WRONLY | O_CREAT | O_TRUNC);
+
+  // check for open error
+  if (!wrfile.isOpen()) {
+    error("MakeTestFile");
+  }
+
+  // write test file
+  wrfile.print(F(
+                 "Line A\r\n"
+                 "Line B\r\n"
+                 "Line C\r\n"
+                 "Line D\r\n"
+                 "Line E\r\n"
+               ));
+  wrfile.close();
+}
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  cout << F("Type any character to start\n");
+  while (!Serial.available()) {
+    yield();
+  }
+
+  // Initialize at the highest speed supported by the board that is
+  // not over 50 MHz. Try a lower speed if SPI errors occur.
+  if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
+    sd.initErrorHalt();
+  }
+
+  makeTestFile();
+
+  demoFgets();
+
+  cout << F("\nDone\n");
+}
+void loop() {}

+ 51 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/readlog/readlog.ino

@@ -0,0 +1,51 @@
+/*
+ * Read the logfile created by the eventlog.ino example.
+ * Demo of pathnames and working directories
+ */
+#include <SPI.h>
+#include "SdFat.h"
+#include "sdios.h"
+
+// SD chip select pin
+const uint8_t chipSelect = SS;
+
+// file system object
+SdFat sd;
+
+// define a serial output stream
+ArduinoOutStream cout(Serial);
+//------------------------------------------------------------------------------
+void setup() {
+  int c;
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  // Initialize at the highest speed supported by the board that is
+  // not over 50 MHz. Try a lower speed if SPI errors occur.
+  if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
+    sd.initErrorHalt();
+  }
+
+  // set current working directory
+  if (!sd.chdir("logs/2014/Jan/")) {
+    sd.errorHalt("chdir failed. Did you run eventlog.ino?");
+  }
+  // open file in current working directory
+  ifstream file("logfile.txt");
+
+  if (!file.is_open()) {
+    sd.errorHalt("open failed");
+  }
+
+  // copy the file to Serial
+  while ((c = file.get()) >= 0) {
+    cout << (char)c;
+  }
+
+  cout << "Done" << endl;
+}
+//------------------------------------------------------------------------------
+void loop() {}

+ 34 - 0
lib/SdFat_NoArduino/examples/examplesV1/#attic/readme.txt

@@ -0,0 +1,34 @@
+Old and debug examples.
+
+AnalogLogger - A simple data logger for one or more analog pins.
+
+append - This sketch creates a large file by successive
+         open/write/close operations.
+
+average - A demonstration of parsing floating point numbers.
+
+BaseExtCaseTest - Long file name test.
+
+benchSD - A read/write benchmark for the standard Arduino SD.h library.
+
+bufstream - ibufsteam to parse a line and obufstream to format a line.
+
+cin_cout - Demo of ArduinoInStream and ArduinoOutStream.             
+
+eventlog - Append a line to a file - demo of pathnames and streams.
+
+fgetsRewrite - Demo of rewriting a line read by fgets.
+
+HelloWorld - Create a serial output stream.
+
+MiniSerial - SdFat minimal serial support for debug.
+
+PrintBenchmarkSD - Bench mark SD.h print.
+
+readlog - Read file. Demo of pathnames and current working directory.
+
+SD_Size - Determine flash used by SD.h example.
+
+SdFatSize - Determine flash used by SdFat.
+
+StreamParseInt - Simple demo of the Stream parsInt() member function.

+ 39 - 0
lib/SdFat_NoArduino/examples/examplesV1/AnalogBinLogger/AnalogBinLogger.h

@@ -0,0 +1,39 @@
+#ifndef AnalogBinLogger_h
+#define AnalogBinLogger_h
+//------------------------------------------------------------------------------
+// First block of file.
+struct metadata_t {
+  unsigned long  adcFrequency;     // ADC clock frequency
+  unsigned long  cpuFrequency;     // CPU clock frequency
+  unsigned long  sampleInterval;   // Sample interval in CPU cycles.
+  unsigned long  recordEightBits;  // Size of ADC values, nonzero for 8-bits.
+  unsigned long  pinCount;         // Number of analog pins in a sample.
+  unsigned long  pinNumber[123];   // List of pin numbers in a sample.
+};
+//------------------------------------------------------------------------------
+// Data block for 8-bit ADC mode.
+const size_t DATA_DIM8 = 508;
+struct block8_t {
+  unsigned short count;    // count of data values
+  unsigned short overrun;  // count of overruns since last block
+  unsigned char  data[DATA_DIM8];
+};
+//------------------------------------------------------------------------------
+// Data block for 10-bit ADC mode.
+const size_t DATA_DIM16 = 254;
+struct block16_t {
+  unsigned short count;    // count of data values
+  unsigned short overrun;  // count of overruns since last block
+  unsigned short data[DATA_DIM16];
+};
+//------------------------------------------------------------------------------
+// Data block for PC use
+struct adcdata_t {
+  unsigned short count;    // count of data values
+  unsigned short overrun;  // count of overruns since last block
+  union {
+    unsigned char  u8[DATA_DIM8];
+    unsigned short u16[DATA_DIM16];
+  } data;
+};
+#endif  // AnalogBinLogger_h

+ 826 - 0
lib/SdFat_NoArduino/examples/examplesV1/AnalogBinLogger/AnalogBinLogger.ino

@@ -0,0 +1,826 @@
+/**
+ * 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 13 512 byte buffers will be used.
+ *
+ * Each 512 byte data block in the file has a four byte header followed by up
+ * to 508 bytes of data. (508 values in 8-bit mode or 254 values in 10-bit mode)
+ * Each block contains an integral number of samples with unused space at the
+ * end of the block.
+ *
+ * Data is written to the file using a SD multiple block write command.
+ */
+#ifdef __AVR__
+#include <SPI.h>
+#include "SdFat.h"
+#include "FreeStack.h"
+#include "AnalogBinLogger.h"
+//------------------------------------------------------------------------------
+// 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
+//------------------------------------------------------------------------------
+// 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)
+//------------------------------------------------------------------------------
+// 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 blocks.
+// The program creates a contiguous file with FILE_BLOCK_COUNT 512 byte blocks.
+// This file is flash erased using special SD commands.  The file will be
+// truncated if logging is stopped early.
+const uint32_t FILE_BLOCK_COUNT = 256000;
+
+// log file base name.  Must be six characters or less.
+#define FILE_BASE_NAME "analog"
+
+// Set RECORD_EIGHT_BITS non-zero to record only the high 8-bits of the ADC.
+#define RECORD_EIGHT_BITS 0
+//------------------------------------------------------------------------------
+// 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 = 3;
+
+// SD chip select pin.
+const uint8_t SD_CS_PIN = SS;
+//------------------------------------------------------------------------------
+// Buffer definitions.
+//
+// The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT additional
+// buffers.  QUEUE_DIM must be a power of two larger than
+//(BUFFER_BLOCK_COUNT + 1).
+//
+#if RAMEND < 0X8FF
+#error Too little SRAM
+//
+#elif RAMEND < 0X10FF
+// Use total of two 512 byte buffers.
+const uint8_t BUFFER_BLOCK_COUNT = 1;
+// Dimension for queues of 512 byte SD blocks.
+const uint8_t QUEUE_DIM = 4;  // Must be a power of two!
+//
+#elif RAMEND < 0X20FF
+// Use total of five 512 byte buffers.
+const uint8_t BUFFER_BLOCK_COUNT = 4;
+// Dimension for queues of 512 byte SD blocks.
+const uint8_t QUEUE_DIM = 8;  // Must be a power of two!
+//
+#elif RAMEND < 0X40FF
+// Use total of 13 512 byte buffers.
+const uint8_t BUFFER_BLOCK_COUNT = 12;
+// Dimension for queues of 512 byte SD blocks.
+const uint8_t QUEUE_DIM = 16;  // Must be a power of two!
+//
+#else  // RAMEND
+// Use total of 29 512 byte buffers.
+const uint8_t BUFFER_BLOCK_COUNT = 28;
+// Dimension for queues of 512 byte SD blocks.
+const uint8_t QUEUE_DIM = 32;  // Must be a power of two!
+#endif  // RAMEND
+//==============================================================================
+// End of configuration constants.
+//==============================================================================
+// Temporary log file.  Will be deleted if a reset or power failure occurs.
+#define TMP_FILE_NAME "tmp_log.bin"
+
+// Size of file base name.  Must not be larger than six.
+const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
+
+// 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, millis, micros.
+const uint16_t ISR_TIMER0 = 160;
+//==============================================================================
+SdFat sd;
+
+SdBaseFile binFile;
+
+char binName[13] = FILE_BASE_NAME "00.bin";
+
+#if RECORD_EIGHT_BITS
+const size_t SAMPLES_PER_BLOCK = DATA_DIM8/PIN_COUNT;
+typedef block8_t block_t;
+#else  // RECORD_EIGHT_BITS
+const size_t SAMPLES_PER_BLOCK = DATA_DIM16/PIN_COUNT;
+typedef block16_t block_t;
+#endif // RECORD_EIGHT_BITS
+
+block_t* emptyQueue[QUEUE_DIM];
+uint8_t emptyHead;
+uint8_t emptyTail;
+
+block_t* fullQueue[QUEUE_DIM];
+volatile uint8_t fullHead;  // volatile insures non-interrupt code sees changes.
+uint8_t fullTail;
+
+// queueNext assumes QUEUE_DIM is a power of two
+inline uint8_t queueNext(uint8_t ht) {
+  return (ht + 1) & (QUEUE_DIM -1);
+}
+//==============================================================================
+// Interrupt Service Routines
+
+// Pointer to current buffer.
+block_t* isrBuf;
+
+// Need new buffer if true.
+bool isrBufNeeded = true;
+
+// 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 (isrBufNeeded && emptyHead == emptyTail) {
+    // no buffers - count overrun
+    if (isrOver < 0XFFFF) {
+      isrOver++;
+    }
+
+    // Avoid missed timer error.
+    timerFlag = false;
+    return;
+  }
+  // Start ADC
+  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;
+  }
+  // Check for buffer needed.
+  if (isrBufNeeded) {
+    // Remove buffer from empty queue.
+    isrBuf = emptyQueue[emptyTail];
+    emptyTail = queueNext(emptyTail);
+    isrBuf->count = 0;
+    isrBuf->overrun = isrOver;
+    isrBufNeeded = false;
+  }
+  // Store ADC data.
+  isrBuf->data[isrBuf->count++] = d;
+
+  // Check for buffer full.
+  if (isrBuf->count >= PIN_COUNT*SAMPLES_PER_BLOCK) {
+    // Put buffer isrIn full queue.
+    uint8_t tmp = fullHead;  // Avoid extra fetch of volatile fullHead.
+    fullQueue[tmp] = (block_t*)isrBuf;
+    fullHead = queueNext(tmp);
+
+    // Set buffer needed and clear overruns.
+    isrBufNeeded = true;
+    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) {sd.errorPrint(F(msg));fatalBlink();}
+//------------------------------------------------------------------------------
+//
+void fatalBlink() {
+  while (true) {
+    if (ERROR_LED_PIN >= 0) {
+      digitalWrite(ERROR_LED_PIN, HIGH);
+      delay(200);
+      digitalWrite(ERROR_LED_PIN, LOW);
+      delay(200);
+    }
+  }
+}
+//==============================================================================
+#if ADPS0 != 0 || ADPS1 != 1 || ADPS2 != 2
+#error unexpected ADC prescaler bits
+#endif
+//------------------------------------------------------------------------------
+// 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 > sizeof(meta->pinNumber)/sizeof(meta->pinNumber[0])) {
+    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;
+    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, 4);
+}
+//------------------------------------------------------------------------------
+// enable ADC and timer1 interrupts
+void adcStart() {
+  // initialize ISR
+  isrBufNeeded = true;
+  isrOver = 0;
+  adcindex = 1;
+
+  // 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;
+}
+//------------------------------------------------------------------------------
+void adcStop() {
+  TIMSK1 = 0;
+  ADCSRA = 0;
+}
+//------------------------------------------------------------------------------
+// Convert binary file to csv file.
+void binaryToCsv() {
+  uint8_t lastPct = 0;
+  block_t buf;
+  metadata_t* pm;
+  uint32_t t0 = millis();
+  char csvName[13];
+  StdioStream csvStream;
+
+  if (!binFile.isOpen()) {
+    Serial.println(F("No current binary file"));
+    return;
+  }
+  binFile.rewind();
+  if (binFile.read(&buf , 512) != 512) {
+    error("Read metadata failed");
+  }
+  // Create a new csv file.
+  strcpy(csvName, binName);
+  strcpy(&csvName[BASE_NAME_SIZE + 3], "csv");
+
+  if (!csvStream.fopen(csvName, "w")) {
+    error("open csvStream failed");
+  }
+  Serial.println();
+  Serial.print(F("Writing: "));
+  Serial.print(csvName);
+  Serial.println(F(" - type any character to stop"));
+  pm = (metadata_t*)&buf;
+  csvStream.print(F("Interval,"));
+  float intervalMicros = 1.0e6*pm->sampleInterval/(float)pm->cpuFrequency;
+  csvStream.print(intervalMicros, 4);
+  csvStream.println(F(",usec"));
+  for (uint8_t i = 0; i < pm->pinCount; i++) {
+    if (i) {
+      csvStream.putc(',');
+    }
+    csvStream.print(F("pin"));
+    csvStream.print(pm->pinNumber[i]);
+  }
+  csvStream.println();
+  uint32_t tPct = millis();
+  while (!Serial.available() && binFile.read(&buf, 512) == 512) {
+    if (buf.count == 0) {
+      break;
+    }
+    if (buf.overrun) {
+      csvStream.print(F("OVERRUN,"));
+      csvStream.println(buf.overrun);
+    }
+    for (uint16_t j = 0; j < buf.count; j += PIN_COUNT) {
+      for (uint16_t i = 0; i < PIN_COUNT; i++) {
+        if (i) {
+          csvStream.putc(',');
+        }
+        csvStream.print(buf.data[i + j]);
+      }
+      csvStream.println();
+    }
+    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 (Serial.available()) {
+      break;
+    }
+  }
+  csvStream.fclose();
+  Serial.print(F("Done: "));
+  Serial.print(0.001*(millis() - t0));
+  Serial.println(F(" Seconds"));
+}
+//------------------------------------------------------------------------------
+// read data file and check for overruns
+void checkOverrun() {
+  bool headerPrinted = false;
+  block_t buf;
+  uint32_t bgnBlock, endBlock;
+  uint32_t bn = 0;
+
+  if (!binFile.isOpen()) {
+    Serial.println(F("No current binary file"));
+    return;
+  }
+  if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
+    error("contiguousRange failed");
+  }
+  binFile.rewind();
+  Serial.println();
+  Serial.println(F("Checking overrun errors - type any character to stop"));
+  if (binFile.read(&buf , 512) != 512) {
+    error("Read metadata failed");
+  }
+  bn++;
+  while (binFile.read(&buf, 512) == 512) {
+    if (buf.count == 0) {
+      break;
+    }
+    if (buf.overrun) {
+      if (!headerPrinted) {
+        Serial.println();
+        Serial.println(F("Overruns:"));
+        Serial.println(F("fileBlockNumber,sdBlockNumber,overrunCount"));
+        headerPrinted = true;
+      }
+      Serial.print(bn);
+      Serial.print(',');
+      Serial.print(bgnBlock + bn);
+      Serial.print(',');
+      Serial.println(buf.overrun);
+    }
+    bn++;
+  }
+  if (!headerPrinted) {
+    Serial.println(F("No errors found"));
+  } else {
+    Serial.println(F("Done"));
+  }
+}
+//------------------------------------------------------------------------------
+// dump data file to Serial
+void dumpData() {
+  block_t buf;
+  if (!binFile.isOpen()) {
+    Serial.println(F("No current binary file"));
+    return;
+  }
+  binFile.rewind();
+  if (binFile.read(&buf , 512) != 512) {
+    error("Read metadata failed");
+  }
+  Serial.println();
+  Serial.println(F("Type any character to stop"));
+  delay(1000);
+  while (!Serial.available() && binFile.read(&buf , 512) == 512) {
+    if (buf.count == 0) {
+      break;
+    }
+    if (buf.overrun) {
+      Serial.print(F("OVERRUN,"));
+      Serial.println(buf.overrun);
+    }
+    for (uint16_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"));
+}
+//------------------------------------------------------------------------------
+// log data
+// max number of blocks to erase per erase call
+uint32_t const ERASE_SIZE = 262144L;
+void logData() {
+  uint32_t bgnBlock, endBlock;
+
+  // Allocate extra buffer space.
+  block_t block[BUFFER_BLOCK_COUNT];
+
+  Serial.println();
+
+  // Initialize ADC and timer1.
+  adcInit((metadata_t*) &block[0]);
+
+  // Find unused file name.
+  if (BASE_NAME_SIZE > 6) {
+    error("FILE_BASE_NAME too long");
+  }
+  while (sd.exists(binName)) {
+    if (binName[BASE_NAME_SIZE + 1] != '9') {
+      binName[BASE_NAME_SIZE + 1]++;
+    } else {
+      binName[BASE_NAME_SIZE + 1] = '0';
+      if (binName[BASE_NAME_SIZE] == '9') {
+        error("Can't create file name");
+      }
+      binName[BASE_NAME_SIZE]++;
+    }
+  }
+  // Delete old tmp file.
+  if (sd.exists(TMP_FILE_NAME)) {
+    Serial.println(F("Deleting tmp file"));
+    if (!sd.remove(TMP_FILE_NAME)) {
+      error("Can't remove tmp file");
+    }
+  }
+  // Create new file.
+  Serial.println(F("Creating new file"));
+  binFile.close();
+  if (!binFile.createContiguous(TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) {
+    error("createContiguous failed");
+  }
+  // Get the address of the file on the SD.
+  if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
+    error("contiguousRange failed");
+  }
+  // Use SdFat's internal buffer.
+  uint8_t* cache = (uint8_t*)sd.vol()->cacheClear();
+  if (cache == 0) {
+    error("cacheClear failed");
+  }
+
+  // Flash erase all data in the file.
+  Serial.println(F("Erasing all data"));
+  uint32_t bgnErase = bgnBlock;
+  uint32_t endErase;
+  while (bgnErase < endBlock) {
+    endErase = bgnErase + ERASE_SIZE;
+    if (endErase > endBlock) {
+      endErase = endBlock;
+    }
+    if (!sd.card()->erase(bgnErase, endErase)) {
+      error("erase failed");
+    }
+    bgnErase = endErase + 1;
+  }
+  // Start a multiple block write.
+  if (!sd.card()->writeStart(bgnBlock, FILE_BLOCK_COUNT)) {
+    error("writeBegin failed");
+  }
+  // Write metadata.
+  if (!sd.card()->writeData((uint8_t*)&block[0])) {
+    error("Write metadata failed");
+  }
+  // Initialize queues.
+  emptyHead = emptyTail = 0;
+  fullHead = fullTail = 0;
+
+  // Use SdFat buffer for one block.
+  emptyQueue[emptyHead] = (block_t*)cache;
+  emptyHead = queueNext(emptyHead);
+
+  // Put rest of buffers in the empty queue.
+  for (uint8_t i = 0; i < BUFFER_BLOCK_COUNT; i++) {
+    emptyQueue[emptyHead] = &block[i];
+    emptyHead = queueNext(emptyHead);
+  }
+  // Give SD time to prepare for big write.
+  delay(1000);
+  Serial.println(F("Logging - type any character to stop"));
+  // Wait for Serial Idle.
+  Serial.flush();
+  delay(10);
+  uint32_t bn = 1;
+  uint32_t t0 = millis();
+  uint32_t t1 = t0;
+  uint32_t overruns = 0;
+  uint32_t count = 0;
+  uint32_t maxLatency = 0;
+
+  // Start logging interrupts.
+  adcStart();
+  while (1) {
+    if (fullHead != fullTail) {
+      // Get address of block to write.
+      block_t* pBlock = fullQueue[fullTail];
+
+      // Write block to SD.
+      uint32_t usec = micros();
+      if (!sd.card()->writeData((uint8_t*)pBlock)) {
+        error("write data failed");
+      }
+      usec = micros() - usec;
+      t1 = millis();
+      if (usec > maxLatency) {
+        maxLatency = usec;
+      }
+      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);
+        }
+      }
+      // Move block to empty queue.
+      emptyQueue[emptyHead] = pBlock;
+      emptyHead = queueNext(emptyHead);
+      fullTail = queueNext(fullTail);
+      bn++;
+      if (bn == FILE_BLOCK_COUNT) {
+        // File full so stop ISR calls.
+        adcStop();
+        break;
+      }
+    }
+    if (timerError) {
+      error("Missed timer event - rate too high");
+    }
+    if (Serial.available()) {
+      // Stop ISR calls.
+      adcStop();
+      if (isrBuf != 0 && isrBuf->count >= PIN_COUNT) {
+        // Truncate to last complete sample.
+        isrBuf->count = PIN_COUNT*(isrBuf->count/PIN_COUNT);
+        // Put buffer in full queue.
+        fullQueue[fullHead] = isrBuf;
+        fullHead = queueNext(fullHead);
+        isrBuf = 0;
+      }
+      if (fullHead == fullTail) {
+        break;
+      }
+    }
+  }
+  if (!sd.card()->writeStop()) {
+    error("writeStop failed");
+  }
+  // Truncate file if recording stopped early.
+  if (bn != FILE_BLOCK_COUNT) {
+    Serial.println(F("Truncating file"));
+    if (!binFile.truncate(512L * bn)) {
+      error("Can't truncate file");
+    }
+  }
+  if (!binFile.rename(binName)) {
+    error("Can't rename file");
+  }
+  Serial.print(F("File renamed: "));
+  Serial.println(binName);
+  Serial.print(F("Max block write usec: "));
+  Serial.println(maxLatency);
+  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("Samples/sec: "));
+  Serial.println((1000.0/PIN_COUNT)*count/(t1-t0));
+  Serial.print(F("Overruns: "));
+  Serial.println(overruns);
+  Serial.println(F("Done"));
+}
+//------------------------------------------------------------------------------
+void setup(void) {
+  if (ERROR_LED_PIN >= 0) {
+    pinMode(ERROR_LED_PIN, OUTPUT);
+  }
+  Serial.begin(9600);
+
+  // Read the first sample pin to init the ADC.
+  analogRead(PIN_LIST[0]);
+
+  Serial.print(F("FreeStack: "));
+  Serial.println(FreeStack());
+
+  // Initialize at the highest speed supported by the board that is
+  // not over 50 MHz. Try a lower speed if SPI errors occur.
+  if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(50))) {
+    sd.initErrorPrint();
+    fatalBlink();
+  }
+}
+//------------------------------------------------------------------------------
+void loop(void) {
+  // Read any Serial data.
+  do {
+    delay(10);
+  } while (Serial.available() && Serial.read() >= 0);
+  Serial.println();
+  Serial.println(F("type:"));
+  Serial.println(F("c - convert file to csv"));
+  Serial.println(F("d - dump data to Serial"));
+  Serial.println(F("e - overrun error details"));
+  Serial.println(F("r - record ADC data"));
+
+  while(!Serial.available()) {
+    yield();
+  }
+  char c = tolower(Serial.read());
+  if (ERROR_LED_PIN >= 0) {
+    digitalWrite(ERROR_LED_PIN, LOW);
+  }
+  // Read any Serial data.
+  do {
+    delay(10);
+  } while (Serial.available() && Serial.read() >= 0);
+
+  if (c == 'c') {
+    binaryToCsv();
+  } else if (c == 'd') {
+    dumpData();
+  } else if (c == 'e') {
+    checkOverrun();
+  } else if (c == 'r') {
+    logData();
+  } else {
+    Serial.println(F("Invalid entry"));
+  }
+}
+#else  // __AVR__
+#error This program is only for AVR.
+#endif  // __AVR__

+ 129 - 0
lib/SdFat_NoArduino/examples/examplesV1/DirectoryFunctions/DirectoryFunctions.ino

@@ -0,0 +1,129 @@
+/*
+ * Example use of chdir(), ls(), mkdir(), and  rmdir().
+ */
+#include <SPI.h>
+#include "SdFat.h"
+#include "sdios.h"
+// SD card chip select pin.
+const uint8_t chipSelect = SS;
+//------------------------------------------------------------------------------
+
+// File system object.
+SdFat sd;
+
+// Directory file.
+SdFile root;
+
+// Use for file creation in folders.
+SdFile file;
+
+// Create a Serial output stream.
+ArduinoOutStream cout(Serial);
+
+// Buffer for Serial input.
+char cinBuf[40];
+
+// Create a serial input stream.
+ArduinoInStream cin(Serial, cinBuf, sizeof(cinBuf));
+//==============================================================================
+// Error messages stored in flash.
+#define error(msg) sd.errorHalt(F(msg))
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+
+  // Wait for USB Serial
+  while (!Serial) {
+    yield();
+  }
+  delay(1000);
+
+  cout << F("Type any character to start\n");
+  // Wait for input line and discard.
+  cin.readline();
+  cout << endl;
+
+  // Initialize at the highest speed supported by the board that is
+  // not over 50 MHz. Try a lower speed if SPI errors occur.
+  if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
+    sd.initErrorHalt();
+  }
+  if (sd.exists("Folder1")
+    || sd.exists("Folder1/file1.txt")
+    || sd.exists("Folder1/File2.txt")) {
+    error("Please remove existing Folder1, file1.txt, and File2.txt");
+  }
+
+  int rootFileCount = 0;
+  if (!root.open("/")) {
+    error("open root failed");
+  }
+  while (file.openNext(&root, O_RDONLY)) {
+    if (!file.isHidden()) {
+      rootFileCount++;
+    }
+    file.close();
+    if (rootFileCount > 10) {
+      error("Too many files in root. Please use an empty SD.");
+    }
+  }
+  if (rootFileCount) {
+    cout << F("\nPlease use an empty SD for best results.\n\n");
+    delay(1000);
+  }
+  // Create a new folder.
+  if (!sd.mkdir("Folder1")) {
+    error("Create Folder1 failed");
+  }
+  cout << F("Created Folder1\n");
+
+  // Create a file in Folder1 using a path.
+  if (!file.open("Folder1/file1.txt", O_WRONLY | O_CREAT)) {
+    error("create Folder1/file1.txt failed");
+  }
+  file.close();
+  cout << F("Created Folder1/file1.txt\n");
+
+  // Change volume working directory to Folder1.
+  if (!sd.chdir("Folder1")) {
+    error("chdir failed for Folder1.\n");
+  }
+  cout << F("chdir to Folder1\n");
+
+  // Create File2.txt in current directory.
+  if (!file.open("File2.txt", O_WRONLY | O_CREAT)) {
+    error("create File2.txt failed");
+  }
+  file.close();
+  cout << F("Created File2.txt in current directory\n");
+
+  cout << F("\nList of files on the SD.\n");
+  sd.ls("/", LS_R);
+
+  // Remove files from current directory.
+  if (!sd.remove("file1.txt") || !sd.remove("File2.txt")) {
+    error("remove failed");
+  }
+  cout << F("\nfile1.txt and File2.txt removed.\n");
+
+  // Change current directory to root.
+  if (!sd.chdir()) {
+    error("chdir to root failed.\n");
+  }
+
+  cout << F("\nList of files on the SD.\n");
+  sd.ls(LS_R);
+
+  // Remove Folder1.
+  if (!sd.rmdir("Folder1")) {
+    error("rmdir for Folder1 failed\n");
+  }
+
+  cout << F("\nFolder1 removed.\n");
+  cout << F("\nList of files on the SD.\n");
+  sd.ls(LS_R);
+  cout << F("Done!\n");
+}
+//------------------------------------------------------------------------------
+// Nothing happens in loop.
+void loop() {}

+ 102 - 0
lib/SdFat_NoArduino/examples/examplesV1/LongFileName/LongFileName.ino

@@ -0,0 +1,102 @@
+// Example use of lfnOpenNext and open by index.
+// You can use test files located in
+// SdFat/examples/LongFileName/testFiles.
+#include<SPI.h>
+#include "SdFat.h"
+#include "FreeStack.h"
+
+// SD card chip select pin.
+const uint8_t SD_CS_PIN = SS;
+
+SdFat sd;
+SdFile file;
+SdFile dirFile;
+
+// Number of files found.
+uint16_t n = 0;
+
+// Max of ten files since files are selected with a single digit.
+const uint16_t nMax = 10;
+
+// Position of file's directory entry.
+uint16_t dirIndex[nMax];
+//------------------------------------------------------------------------------
+void setup() {
+  Serial.begin(9600);
+  while (!Serial) {}
+  delay(1000);
+
+  // Print the location of some test files.
+  Serial.println(F("\r\n"
+                   "You can use test files located in\r\n"
+                   "SdFat/examples/LongFileName/testFiles"));
+
+  // Initialize at the highest speed supported by the board that is
+  // not over 50 MHz. Try a lower speed if SPI errors occur.
+  if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(50))) {
+    sd.initErrorHalt();
+  }
+  Serial.print(F("FreeStack: "));
+  Serial.println(FreeStack());
+  Serial.println();
+
+  // List files in root directory.
+  if (!dirFile.open("/", O_RDONLY)) {
+    sd.errorHalt("open root failed");
+  }
+  while (n < nMax && file.openNext(&dirFile, O_RDONLY)) {
+
+    // Skip directories and hidden files.
+    if (!file.isSubDir() && !file.isHidden()) {
+
+      // Save dirIndex of file in directory.
+      dirIndex[n] = file.dirIndex();
+
+      // Print the file number and name.
+      Serial.print(n++);
+      Serial.write(' ');
+      file.printName(&Serial);
+      Serial.println();
+    }
+    file.close();
+  }
+}
+//------------------------------------------------------------------------------
+void loop() {
+  int c;
+
+  // Read any existing Serial data.
+  do {
+    delay(10);
+  } while (Serial.available() && Serial.read() >= 0);
+  Serial.print(F("\r\nEnter File Number: "));
+
+  while (!Serial.available()) {
+    yield();
+  }
+  c = Serial.read();
+  uint8_t i = c - '0';
+  if (!isdigit(c) || i >= n) {
+    Serial.println(F("Invald number"));
+    return;
+  }
+  Serial.println(i);
+  if (!file.open(&dirFile, dirIndex[i], O_RDONLY)) {
+    sd.errorHalt(F("open"));
+  }
+  Serial.println();
+
+  char last = 0;
+
+  // Copy up to 500 characters to Serial.
+  for (int k = 0; k < 500 && (c = file.read()) > 0; k++)  {
+    Serial.write(last = (char)c);
+  }
+  // Add new line if missing from last line.
+  if (last != '\n') {
+    Serial.println();
+  }
+  file.close();
+  Serial.flush();
+  delay(100);
+}

+ 4 - 0
lib/SdFat_NoArduino/examples/examplesV1/LongFileName/testFiles/A long name can be 255 characters.txt

@@ -0,0 +1,4 @@
+This is "A long name can be 255 characters.txt"
+This file has a typical Long File Name.
+
+The maximum length of a Long File Name is 255 characters.

+ 1 - 0
lib/SdFat_NoArduino/examples/examplesV1/LongFileName/testFiles/LFN,NAME.TXT

@@ -0,0 +1 @@
+LFN,NAME.TXT is not 8.3 since it has a comma.

+ 5 - 0
lib/SdFat_NoArduino/examples/examplesV1/LongFileName/testFiles/MIXCASE.txt

@@ -0,0 +1,5 @@
+MIXCASE.txt does not have a Long File Name.
+
+Starting with NT, file names of this form,
+have the basename and extension character case
+encoded in two bits of the 8.3 directory entry.

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.