diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1179b93..6d4cdc5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,8 +14,8 @@ If you don't find anything, please [open a new issue](https://github.com/khoih-p Please ensure to specify the following: -* Arduino IDE version (e.g. 1.8.16) or Platform.io version -* `Arduino mbed_rp2040` Core Version (e.g. Arduino mbed_rp2040 core v2.5.2) or `RP2040` Core Version (e.g. RP2040 core v1.9.5) +* Arduino IDE version (e.g. 1.8.19) or Platform.io version +* `Arduino mbed_rp2040` Core Version (e.g. Arduino mbed_rp2040 core v3.4.1) or `RP2040` Core Version (e.g. RP2040 core v2.6.3) * `RP2040` Board type (e.g. Nano_RP2040_Connect, RASPBERRY_PI_PICO, ADAFRUIT_FEATHER_RP2040, GENERIC_RP2040, etc.) * Contextual information (e.g. what you were trying to achieve) * Simplest possible steps to reproduce @@ -27,14 +27,14 @@ Please ensure to specify the following: ### Example ``` -Arduino IDE version: 1.8.16 -Arduino mbed_rp2040 core v2.5.2 +Arduino IDE version: 1.8.19 +Arduino mbed_rp2040 core v3.4.1 RASPBERRY_PI_PICO board OS: Ubuntu 20.04 LTS -Linux xy-Inspiron-3593 5.4.0-86-generic #97-Ubuntu SMP Fri Sep 17 19:19:40 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux +Linux xy-Inspiron-3593 5.15.0-53-generic #59~20.04.1-Ubuntu SMP Thu Oct 20 15:10:22 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux Context: -I encountered a crash while using TimerInterrupt. +I encountered a crash while using this library Steps to reproduce: 1. ... @@ -43,12 +43,32 @@ Steps to reproduce: 4. ... ``` +--- + ### Sending Feature Requests Feel free to post feature requests. It's helpful if you can explain exactly why the feature would be useful. There are usually some outstanding feature requests in the [existing issues list](https://github.com/khoih-prog/RP2040_SD/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement), feel free to add comments to them. +--- + ### Sending Pull Requests Pull Requests with changes and fixes are also welcome! + +Please use the `astyle` to reformat the updated library code as follows (demo for Ubuntu Linux) + +1. Change directory to the library GitHub + +``` +xy@xy-Inspiron-3593:~$ cd Arduino/xy/RP2040_SD_GitHub/ +xy@xy-Inspiron-3593:~/Arduino/xy/RP2040_SD_GitHub$ +``` + +2. Issue astyle command + +``` +xy@xy-Inspiron-3593:~/Arduino/xy/RP2040_SD_GitHub$ bash utils/restyle.sh +``` + diff --git a/README.md b/README.md index 65bd0fd..45810c0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,11 @@ [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#Contributing) [![GitHub issues](https://img.shields.io/github/issues/khoih-prog/RP2040_SD.svg)](http://github.com/khoih-prog/RP2040_SD/issues) -Buy Me A Coffee + +Donate to my libraries using BuyMeACoffee + + + --- --- @@ -98,8 +102,8 @@ But if you need to write codes to be used in both [**Arduino-mbed RP2040** core] ## Prerequisites 1. [`Arduino IDE 1.8.19+` for Arduino](https://github.com/arduino/Arduino). [![GitHub release](https://img.shields.io/github/release/arduino/Arduino.svg)](https://github.com/arduino/Arduino/releases/latest) -2. [`Arduino mbed_rp2040 core 2.7.2+`](https://github.com/arduino/ArduinoCore-mbed) for Arduino (Use Arduino Board Manager) RP2040-based boards, such as **Arduino Nano RP2040 Connect, RASPBERRY_PI_PICO, etc.**. [![GitHub release](https://img.shields.io/github/release/arduino/ArduinoCore-mbed.svg)](https://github.com/arduino/ArduinoCore-mbed/releases/latest) -3. [`Earle Philhower's arduino-pico core v1.10.0+`](https://github.com/earlephilhower/arduino-pico) for RP2040-based boards such as **RASPBERRY_PI_PICO, ADAFRUIT_FEATHER_RP2040 and GENERIC_RP2040**, etc. [![GitHub release](https://img.shields.io/github/release/earlephilhower/arduino-pico.svg)](https://github.com/earlephilhower/arduino-pico/releases/latest) +2. [`Arduino mbed_rp2040 core 3.4.1+`](https://github.com/arduino/ArduinoCore-mbed) for Arduino (Use Arduino Board Manager) RP2040-based boards, such as **Arduino Nano RP2040 Connect, RASPBERRY_PI_PICO, etc.**. [![GitHub release](https://img.shields.io/github/release/arduino/ArduinoCore-mbed.svg)](https://github.com/arduino/ArduinoCore-mbed/releases/latest) +3. [`Earle Philhower's arduino-pico core v2.6.3+`](https://github.com/earlephilhower/arduino-pico) for RP2040-based boards such as **RASPBERRY_PI_PICO, ADAFRUIT_FEATHER_RP2040 and GENERIC_RP2040**, etc. [![GitHub release](https://img.shields.io/github/release/earlephilhower/arduino-pico.svg)](https://github.com/earlephilhower/arduino-pico/releases/latest) --- --- @@ -124,7 +128,7 @@ Another way to install is to: 1. Install [VS Code](https://code.visualstudio.com/) 2. Install [PlatformIO](https://platformio.org/platformio-ide) -3. Install [**RP2040_SD** library](https://platformio.org/lib/show/12454/RP2040_SD) or [**RP2040_SD** library](https://platformio.org/lib/show/12454/RP2040_SD) by using [Library Manager](https://platformio.org/lib/show/12454/RP2040_SD/installation). Search for **RP2040_SD** in [Platform.io Author's Libraries](https://platformio.org/lib/search?query=author:%22Khoi%20Hoang%22) +3. Install [**RP2040_SD** library](https://registry.platformio.org/libraries/khoih-prog/RP2040_SD) by using [Library Manager](https://registry.platformio.org/libraries/khoih-prog/RP2040_SD/installation). Search for **RP2040_SD** in [Platform.io Author's Libraries](https://platformio.org/lib/search?query=author:%22Khoi%20Hoang%22) 4. Use included [platformio.ini](platformio/platformio.ini) file from examples to ensure that all dependent libraries will installed automatically. Please visit documentation for the other options and examples at [Project Configuration File](https://docs.platformio.org/page/projectconf.html) @@ -149,7 +153,7 @@ Another way to install is to: #### 1. File [ReadWrite.ino](examples/ReadWrite/ReadWrite.ino) -``` +```cpp /* SD card connection @@ -292,7 +296,7 @@ void loop() The following is the sample terminal output when running example [CardInfo](examples/CardInfo) on MBED RaspberryPi Pico using [**Arduino-mbed RP2040** core](https://github.com/arduino/ArduinoCore-mbed) -``` +```cpp Starting SD Card CardInfo on MBED RaspberryPi Pico MBED RP2040_SD v1.0.1 Initializing SD card with SS = 5 @@ -331,7 +335,7 @@ NEWDATA.TXT 18 The following is the sample terminal output when running example [DumpFile](examples/DumpFile) on MBED RaspberryPi Pico using [**Arduino-mbed RP2040** core](https://github.com/arduino/ArduinoCore-mbed) -``` +```cpp Starting SD Card DumpFile on MBED RaspberryPi Pico MBED RP2040_SD v1.0.1 Initializing SD card with SS = 5 @@ -351,7 +355,7 @@ Testing RP2040 SD The following is the sample terminal output when running example [ListFiles](examples/ListFiles) on MBED RaspberryPi Pico using [**Arduino-mbed RP2040** core](https://github.com/arduino/ArduinoCore-mbed) -``` +```cpp Starting SD Card ListFiles on MBED RaspberryPi Pico MBED RP2040_SD v1.0.1 Initializing SD card with SS = 5 @@ -391,7 +395,7 @@ Print Directory done! The following is the sample terminal output when running example [ReadWrite](examples/ReadWrite) on MBED RaspberryPi Pico using [**Arduino-mbed RP2040** core](https://github.com/arduino/ArduinoCore-mbed) -``` +```cpp Starting SD Card ReadWrite on MBED RaspberryPi Pico MBED RP2040_SD v1.0.1 Initializing SD card with SS = 5 @@ -424,7 +428,7 @@ Testing writing to newtest0.txt The following is the sample terminal output when running example [Files](examples/Files) on MBED RaspberryPi Pico using [**Arduino-mbed RP2040** core](https://github.com/arduino/ArduinoCore-mbed) -``` +```cpp Starting SD Card Files on MBED RaspberryPi Pico MBED RP2040_SD v1.0.1 Initializing SD card with SS = 5 @@ -450,7 +454,7 @@ example.txt doesn't exist. The following is the sample terminal output when running example [CardInfo](examples/CardInfo) on RASPBERRY_PI_PICO using [**arduino-pico core**](https://github.com/earlephilhower/arduino-pico) -``` +```cpp Starting SD Card CardInfo on RASPBERRY_PI_PICO RP2040_SD v1.0.1 Initializing SD card with SS = 17 @@ -487,7 +491,7 @@ NEWDATA.TXT 54 The following is the sample terminal output when running example [DumpFile](examples/DumpFile) on RASPBERRY_PI_PICO using [**arduino-pico core**](https://github.com/earlephilhower/arduino-pico) -``` +```cpp Starting SD Card DumpFile on RASPBERRY_PI_PICO RP2040_SD v1.0.1 Initializing SD card with SS = 17 @@ -507,7 +511,7 @@ Testing RP2040 SD The following is the sample terminal output when running example [ListFiles](examples/ListFiles) on RASPBERRY_PI_PICO using [**arduino-pico core**](https://github.com/earlephilhower/arduino-pico) -``` +```cpp Starting SD Card ListFiles on RASPBERRY_PI_PICO RP2040_SD v1.0.1 Initializing SD card with SS = 17 @@ -541,7 +545,7 @@ Print Directory done! The following is the sample terminal output when running example [ReadWrite](examples/ReadWrite) on RASPBERRY_PI_PICO using [**arduino-pico core**](https://github.com/earlephilhower/arduino-pico) -``` +```cpp Starting SD Card ReadWrite on RASPBERRY_PI_PICO RP2040_SD v1.0.1 Initializing SD card with SS = 17 @@ -574,7 +578,7 @@ Testing writing to newtest0.txt The following is the sample terminal output when running example [Files](examples/Files) on RASPBERRY_PI_PICO using [**arduino-pico core**](https://github.com/earlephilhower/arduino-pico) -``` +```cpp Starting SD Card Files on RASPBERRY_PI_PICO RP2040_SD v1.0.1 Initializing SD card with SS = 17 @@ -591,7 +595,6 @@ readData = 0xDEADBEEF example.txt exists. Removing example.txt... example.txt doesn't exist. - ``` --- @@ -639,6 +642,7 @@ Submit issues to: [RP2040_SD issues](https://github.com/khoih-prog/RP2040_SD/iss 1. Basic SD-wrapper library for **RP2040-based boards, such as NANO_RP2040_CONNECT, RASPBERRY_PI_PICO, ADAFRUIT_FEATHER_RP2040 and GENERIC_RP2040**, using [**Arduino-mbed RP2040** core](https://github.com/arduino/ArduinoCore-mbed) or [**Earle Philhower's arduino-pico core**](https://github.com/earlephilhower/arduino-pico). 2. Add Version String 3. Add Table of Contents +4. Add astyle using `allman` style. Restyle the library --- --- diff --git a/changelog.md b/changelog.md index 1b9b547..a32dc48 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,12 @@ [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#Contributing) [![GitHub issues](https://img.shields.io/github/issues/khoih-prog/RP2040_SD.svg)](http://github.com/khoih-prog/RP2040_SD/issues) + +Donate to my libraries using BuyMeACoffee + + + + --- --- diff --git a/examples/CardInfo/CardInfo.ino b/examples/CardInfo/CardInfo.ino index efd9e19..edc2286 100644 --- a/examples/CardInfo/CardInfo.ino +++ b/examples/CardInfo/CardInfo.ino @@ -1,25 +1,25 @@ /**************************************************************************************************************************** CardInfo.ino - + For all RP2040 boads using Arduimo-mbed or arduino-pico core - + RP2040_SD is a library enable the usage of SD on RP2040-based boards - + This Library 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 Library 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 the Arduino SdFat Library. + You should have received a copy of the GNU General Public License along with the Arduino SdFat Library. If not, see . - + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - + (C) Copyright 2009 by William Greiman (C) Copyright 2010 SparkFun Electronics (C) Copyright 2021 by Khoi Hoang - + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD Licensed under GPL-3.0 license *****************************************************************************************************************************/ @@ -48,7 +48,7 @@ #endif #if defined(ARDUINO_ARCH_MBED) - + #define PIN_SD_MOSI PIN_SPI_MOSI #define PIN_SD_MISO PIN_SPI_MISO #define PIN_SD_SCK PIN_SPI_SCK @@ -60,7 +60,7 @@ #define PIN_SD_MISO PIN_SPI0_MISO #define PIN_SD_SCK PIN_SPI0_SCK #define PIN_SD_SS PIN_SPI0_SS - + #endif #define _RP2040_SD_LOGLEVEL_ 1 @@ -75,45 +75,46 @@ File root; const int chipSelect = PIN_SD_SS; -void printDirectory(File dir, int numTabs) +void printDirectory(File dir, int numTabs) { - while (true) + while (true) { File entry = dir.openNextFile(); - if (! entry) + if (! entry) { // no more files break; } - - for (uint8_t i = 0; i < numTabs; i++) + + for (uint8_t i = 0; i < numTabs; i++) { Serial.print('\t'); } - + Serial.print(entry.name()); - - if (entry.isDirectory()) + + if (entry.isDirectory()) { Serial.println("/"); printDirectory(entry, numTabs + 1); - } - else + } + else { // files have sizes, directories do not Serial.print("\t\t"); Serial.println(entry.size(), DEC); } - + entry.close(); } } -void setup() +void setup() { // Open serial communications and wait for port to open: Serial.begin(115200); + while (!Serial); delay(1000); @@ -123,26 +124,31 @@ void setup() #else Serial.print("Starting SD Card CardInfo on "); #endif - + Serial.println(BOARD_NAME); Serial.println(RP2040_SD_VERSION); - - Serial.print("Initializing SD card with SS = "); Serial.println(PIN_SD_SS); - Serial.print("SCK = "); Serial.println(PIN_SD_SCK); - Serial.print("MOSI = "); Serial.println(PIN_SD_MOSI); - Serial.print("MISO = "); Serial.println(PIN_SD_MISO); + + Serial.print("Initializing SD card with SS = "); + Serial.println(PIN_SD_SS); + Serial.print("SCK = "); + Serial.println(PIN_SD_SCK); + Serial.print("MOSI = "); + Serial.println(PIN_SD_MOSI); + Serial.print("MISO = "); + Serial.println(PIN_SD_MISO); // we'll use the initialization code from the utility libraries // since we're just testing if the card is working! - if (!card.init(SPI_HALF_SPEED, chipSelect)) + if (!card.init(SPI_HALF_SPEED, chipSelect)) { Serial.println("initialization failed. Things to check:"); Serial.println("* is a card inserted?"); Serial.println("* is your wiring correct?"); Serial.println("* did you change the chipSelect pin to match your shield or module?"); + while (1); - } - else + } + else { Serial.println("Wiring is correct and a card is present."); } @@ -150,26 +156,30 @@ void setup() // print the type of card Serial.println(); Serial.print("Card type: "); - - switch (card.type()) + + switch (card.type()) { case SD_CARD_TYPE_SD1: Serial.println("SD1"); break; + case SD_CARD_TYPE_SD2: Serial.println("SD2"); break; + case SD_CARD_TYPE_SDHC: Serial.println("SDHC"); break; + default: Serial.println("Unknown"); } // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32 - if (!volume.init(card)) + if (!volume.init(card)) { Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card"); + while (1); } @@ -198,12 +208,12 @@ void setup() Serial.print("Volume size (Gb): "); Serial.println((float)volumesize / 1024.0); - if (!SD.begin(PIN_SD_SS)) + if (!SD.begin(PIN_SD_SS)) { Serial.println("Initialization failed!"); return; } - + Serial.println("Initialization done."); Serial.println("\nFiles found on the card (name, date and size in bytes): "); @@ -213,6 +223,6 @@ void setup() printDirectory(root, 0); } -void loop() +void loop() { } diff --git a/examples/DataLogger/DataLogger.ino b/examples/DataLogger/DataLogger.ino index 428a444..b107c14 100644 --- a/examples/DataLogger/DataLogger.ino +++ b/examples/DataLogger/DataLogger.ino @@ -1,25 +1,25 @@ /**************************************************************************************************************************** DataLogger.ino - + For all RP2040 boads using Arduimo-mbed or arduino-pico core - + RP2040_SD is a library enable the usage of SD on RP2040-based boards - + This Library 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 Library 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 the Arduino SdFat Library. + You should have received a copy of the GNU General Public License along with the Arduino SdFat Library. If not, see . - + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - + (C) Copyright 2009 by William Greiman (C) Copyright 2010 SparkFun Electronics (C) Copyright 2021 by Khoi Hoang - + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD Licensed under GPL-3.0 license *****************************************************************************************************************************/ @@ -48,7 +48,7 @@ #endif #if defined(ARDUINO_ARCH_MBED) - + #define PIN_SD_MOSI PIN_SPI_MOSI #define PIN_SD_MISO PIN_SPI_MISO #define PIN_SD_SCK PIN_SPI_SCK @@ -60,7 +60,7 @@ #define PIN_SD_MISO PIN_SPI0_MISO #define PIN_SD_SCK PIN_SPI0_SCK #define PIN_SD_SS PIN_SPI0_SS - + #endif #define _RP2040_SD_LOGLEVEL_ 0 @@ -68,10 +68,11 @@ #include #include -void setup() +void setup() { // Open serial communications and wait for port to open: Serial.begin(115200); + while (!Serial); delay(1000); @@ -81,59 +82,64 @@ void setup() #else Serial.print("Starting SD Card ReadWrite on "); #endif - + Serial.println(BOARD_NAME); Serial.println(RP2040_SD_VERSION); - - Serial.print("Initializing SD card with SS = "); Serial.println(PIN_SD_SS); - Serial.print("SCK = "); Serial.println(PIN_SD_SCK); - Serial.print("MOSI = "); Serial.println(PIN_SD_MOSI); - Serial.print("MISO = "); Serial.println(PIN_SD_MISO); - if (!SD.begin(PIN_SD_SS)) + Serial.print("Initializing SD card with SS = "); + Serial.println(PIN_SD_SS); + Serial.print("SCK = "); + Serial.println(PIN_SD_SCK); + Serial.print("MOSI = "); + Serial.println(PIN_SD_MOSI); + Serial.print("MISO = "); + Serial.println(PIN_SD_MISO); + + if (!SD.begin(PIN_SD_SS)) { Serial.println("Initialization failed!"); return; } - + Serial.println("Initialization done."); } -void loop() +void loop() { // make a string for assembling the data to log: String dataString = ""; // read three sensors and append to the string: - for (int analogPin = 0; analogPin < 3; analogPin++) + for (int analogPin = 0; analogPin < 3; analogPin++) { int sensor = analogRead(analogPin); dataString += String(sensor); - - if (analogPin < 2) + + if (analogPin < 2) { dataString += ","; } } - #define fileName "datalog.txt" +#define fileName "datalog.txt" // open the file. note that only one file can be open at a time, // so you have to close this one before opening another. File dataFile = SD.open("datalog.txt", FILE_WRITE); // if the file is available, write to it: - if (dataFile) + if (dataFile) { dataFile.println(dataString); dataFile.close(); - + // print to the serial port too: Serial.println(dataString); } // if the file isn't open, pop up an error: - else + else { - Serial.print("Error opening "); Serial.println(fileName); + Serial.print("Error opening "); + Serial.println(fileName); } } diff --git a/examples/DumpFile/DumpFile.ino b/examples/DumpFile/DumpFile.ino index 6058be7..f6066bc 100644 --- a/examples/DumpFile/DumpFile.ino +++ b/examples/DumpFile/DumpFile.ino @@ -1,25 +1,25 @@ /**************************************************************************************************************************** DumpFile.ino - + For all RP2040 boads using Arduimo-mbed or arduino-pico core - + RP2040_SD is a library enable the usage of SD on RP2040-based boards - + This Library 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 Library 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 the Arduino SdFat Library. + You should have received a copy of the GNU General Public License along with the Arduino SdFat Library. If not, see . - + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - + (C) Copyright 2009 by William Greiman (C) Copyright 2010 SparkFun Electronics (C) Copyright 2021 by Khoi Hoang - + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD Licensed under GPL-3.0 license *****************************************************************************************************************************/ @@ -48,7 +48,7 @@ #endif #if defined(ARDUINO_ARCH_MBED) - + #define PIN_SD_MOSI PIN_SPI_MOSI #define PIN_SD_MISO PIN_SPI_MISO #define PIN_SD_SCK PIN_SPI_SCK @@ -60,7 +60,7 @@ #define PIN_SD_MISO PIN_SPI0_MISO #define PIN_SD_SCK PIN_SPI0_SCK #define PIN_SD_SS PIN_SPI0_SS - + #endif #define _RP2040_SD_LOGLEVEL_ 4 @@ -70,10 +70,11 @@ File myFile; -void setup() +void setup() { // Open serial communications and wait for port to open: Serial.begin(115200); + while (!Serial); delay(1000); @@ -83,52 +84,57 @@ void setup() #else Serial.print("Starting SD Card DumpFile on "); #endif - + Serial.println(BOARD_NAME); Serial.println(RP2040_SD_VERSION); - - Serial.print("Initializing SD card with SS = "); Serial.println(PIN_SD_SS); - Serial.print("SCK = "); Serial.println(PIN_SD_SCK); - Serial.print("MOSI = "); Serial.println(PIN_SD_MOSI); - Serial.print("MISO = "); Serial.println(PIN_SD_MISO); - if (!SD.begin(PIN_SD_SS)) + Serial.print("Initializing SD card with SS = "); + Serial.println(PIN_SD_SS); + Serial.print("SCK = "); + Serial.println(PIN_SD_SCK); + Serial.print("MOSI = "); + Serial.println(PIN_SD_MOSI); + Serial.print("MISO = "); + Serial.println(PIN_SD_MISO); + + if (!SD.begin(PIN_SD_SS)) { Serial.println("Initialization failed!"); return; } - + Serial.println("Initialization done."); // open the file. note that only one file can be open at a time, // so you have to close this one before opening another. - #define fileName "demo.txt" - +#define fileName "demo.txt" + File dataFile = SD.open(fileName, FILE_READ); // if the file is available, read from it: - if (dataFile) + if (dataFile) { Serial.println("====== Data ======"); - - while (dataFile.available()) + + while (dataFile.available()) { Serial.write(dataFile.read()); } - + Serial.println("\n====== Data ======"); - + dataFile.close(); } // if the file isn't open, pop up an error: - else + else { - Serial.print("Error opening "); Serial.println(fileName); + Serial.print("Error opening "); + Serial.println(fileName); } } -void loop() +void loop() { } diff --git a/examples/Files/Files.ino b/examples/Files/Files.ino index 1449900..9751042 100644 --- a/examples/Files/Files.ino +++ b/examples/Files/Files.ino @@ -1,25 +1,25 @@ /**************************************************************************************************************************** Files.ino - + For all RP2040 boads using Arduimo-mbed or arduino-pico core - + RP2040_SD is a library enable the usage of SD on RP2040-based boards - + This Library 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 Library 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 the Arduino SdFat Library. + You should have received a copy of the GNU General Public License along with the Arduino SdFat Library. If not, see . - + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - + (C) Copyright 2009 by William Greiman (C) Copyright 2010 SparkFun Electronics (C) Copyright 2021 by Khoi Hoang - + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD Licensed under GPL-3.0 license *****************************************************************************************************************************/ @@ -47,7 +47,7 @@ #endif #if defined(ARDUINO_ARCH_MBED) - + #define PIN_SD_MOSI PIN_SPI_MOSI #define PIN_SD_MISO PIN_SPI_MISO #define PIN_SD_SCK PIN_SPI_SCK @@ -59,7 +59,7 @@ #define PIN_SD_MISO PIN_SPI0_MISO #define PIN_SD_SCK PIN_SPI0_SCK #define PIN_SD_SS PIN_SPI0_SS - + #endif #define _RP2040_SD_LOGLEVEL_ 4 @@ -97,6 +97,7 @@ void setup() { // Open serial communications and wait for port to open: Serial.begin(115200); + while (!Serial); delay(1000); @@ -110,37 +111,44 @@ void setup() Serial.println(BOARD_NAME); Serial.println(RP2040_SD_VERSION); - Serial.print("Initializing SD card with SS = "); Serial.println(PIN_SD_SS); - Serial.print("SCK = "); Serial.println(PIN_SD_SCK); - Serial.print("MOSI = "); Serial.println(PIN_SD_MOSI); - Serial.print("MISO = "); Serial.println(PIN_SD_MISO); + Serial.print("Initializing SD card with SS = "); + Serial.println(PIN_SD_SS); + Serial.print("SCK = "); + Serial.println(PIN_SD_SCK); + Serial.print("MOSI = "); + Serial.println(PIN_SD_MOSI); + Serial.print("MISO = "); + Serial.println(PIN_SD_MISO); - if (!SD.begin(PIN_SD_SS)) + if (!SD.begin(PIN_SD_SS)) { Serial.println("Initialization failed!"); return; } Serial.println("Initialization done."); - + checkFileExist(fileName); // open a new file and immediately close it: - Serial.print("Creating "); Serial.println(fileName); - + Serial.print("Creating "); + Serial.println(fileName); + myFile = SD.open(fileName, FILE_WRITE); if (myFile) { myFile.write((uint8_t *) &writeData, sizeof(writeData)); - Serial.print("writeData = 0x"); Serial.println(writeData, HEX); + Serial.print("writeData = 0x"); + Serial.println(writeData, HEX); - myFile.close(); + myFile.close(); } else { - Serial.print("Error open for writing "); Serial.println(fileName); + Serial.print("Error open for writing "); + Serial.println(fileName); } myFile = SD.open(fileName, FILE_READ); @@ -149,13 +157,15 @@ void setup() { myFile.read((uint8_t *) &readData, sizeof(readData)); - Serial.print("readData = 0x"); Serial.println(readData, HEX); + Serial.print("readData = 0x"); + Serial.println(readData, HEX); myFile.close(); } else { - Serial.print("Error open for reading "); Serial.println(fileName); + Serial.print("Error open for reading "); + Serial.println(fileName); } checkFileExist(fileName); diff --git a/examples/ListFiles/ListFiles.ino b/examples/ListFiles/ListFiles.ino index 699f828..b51d6d6 100644 --- a/examples/ListFiles/ListFiles.ino +++ b/examples/ListFiles/ListFiles.ino @@ -1,25 +1,25 @@ /**************************************************************************************************************************** ListFiles.ino - + For all RP2040 boads using Arduimo-mbed or arduino-pico core - + RP2040_SD is a library enable the usage of SD on RP2040-based boards - + This Library 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 Library 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 the Arduino SdFat Library. + You should have received a copy of the GNU General Public License along with the Arduino SdFat Library. If not, see . - + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - + (C) Copyright 2009 by William Greiman (C) Copyright 2010 SparkFun Electronics (C) Copyright 2021 by Khoi Hoang - + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD Licensed under GPL-3.0 license *****************************************************************************************************************************/ @@ -48,7 +48,7 @@ #endif #if defined(ARDUINO_ARCH_MBED) - + #define PIN_SD_MOSI PIN_SPI_MOSI #define PIN_SD_MISO PIN_SPI_MISO #define PIN_SD_SCK PIN_SPI_SCK @@ -60,7 +60,7 @@ #define PIN_SD_MISO PIN_SPI0_MISO #define PIN_SD_SCK PIN_SPI0_SCK #define PIN_SD_SS PIN_SPI0_SS - + #endif #include @@ -68,10 +68,11 @@ File root; -void setup() +void setup() { // Open serial communications and wait for port to open: Serial.begin(115200); + while (!Serial); delay(1000); @@ -81,21 +82,25 @@ void setup() #else Serial.print("Starting SD Card ListFiles on "); #endif - + Serial.println(BOARD_NAME); Serial.println(RP2040_SD_VERSION); - - Serial.print("Initializing SD card with SS = "); Serial.println(PIN_SD_SS); - Serial.print("SCK = "); Serial.println(PIN_SD_SCK); - Serial.print("MOSI = "); Serial.println(PIN_SD_MOSI); - Serial.print("MISO = "); Serial.println(PIN_SD_MISO); - if (!SD.begin(PIN_SD_SS)) + Serial.print("Initializing SD card with SS = "); + Serial.println(PIN_SD_SS); + Serial.print("SCK = "); + Serial.println(PIN_SD_SCK); + Serial.print("MOSI = "); + Serial.println(PIN_SD_MOSI); + Serial.print("MISO = "); + Serial.println(PIN_SD_MISO); + + if (!SD.begin(PIN_SD_SS)) { Serial.println("Initialization failed!"); return; } - + Serial.println("Initialization done."); root = SD.open("/"); @@ -104,106 +109,111 @@ void setup() Serial.println("Print Directory done!"); - #define fileName "datalog.txt" - #define fileName1 "newdata.txt" +#define fileName "datalog.txt" +#define fileName1 "newdata.txt" - File myWFile = SD.open(fileName, FILE_WRITE); + File myWFile = SD.open(fileName, FILE_WRITE); if (myWFile) { char writeData[] = "Testing RP2040 SD"; - + myWFile.write((uint8_t *) &writeData, sizeof(writeData)); - Serial.print("writeData = "); Serial.println(writeData); + Serial.print("writeData = "); + Serial.println(writeData); myWFile.close(); } else { - Serial.print("Error open for writing "); Serial.println(fileName); + Serial.print("Error open for writing "); + Serial.println(fileName); } - myWFile = SD.open(fileName1, FILE_WRITE); + myWFile = SD.open(fileName1, FILE_WRITE); if (myWFile) { char writeData[] = "Testing RP2040 SD"; //myWFile.seek(0, (SeekMode) SEEK_END); - + myWFile.write((uint8_t *) &writeData, sizeof(writeData)); myWFile.close(); } else { - Serial.print("Error open for writing "); Serial.println(fileName1); + Serial.print("Error open for writing "); + Serial.println(fileName1); } // OK here to read file - File myRFile = SD.open(fileName, FILE_READ); - + File myRFile = SD.open(fileName, FILE_READ); + if (myRFile) { char readData[64] = "\0"; //myWFile.seek(0, (SeekMode) SEEK_END); - + myRFile.read((uint8_t *) &readData, sizeof(readData) - 1); myRFile.close(); - Serial.print("readData = "); Serial.println(readData); + Serial.print("readData = "); + Serial.println(readData); } else { - Serial.print("Error open for reading "); Serial.println(fileName); + Serial.print("Error open for reading "); + Serial.println(fileName); } root = SD.open("/"); - + printDirectory(root, 0); Serial.println("Print Directory done!"); } -void loop() +void loop() { // nothing happens after setup finishes. } -void printDirectory(File dir, int numTabs) +void printDirectory(File dir, int numTabs) { - while (true) + while (true) { File entry = dir.openNextFile(); - if (! entry) + if (! entry) { // no more files break; } - - for (uint8_t i = 0; i < numTabs; i++) + + for (uint8_t i = 0; i < numTabs; i++) { Serial.print('\t'); } - + Serial.print(entry.name()); - - if (entry.isDirectory()) + + if (entry.isDirectory()) { Serial.println("/"); printDirectory(entry, numTabs + 1); - } - else + } + else { // files have sizes, directories do not Serial.print("\t\t"); Serial.println(entry.size(), DEC); } - + entry.close(); } } diff --git a/examples/NonBlockingWrite/NonBlockingWrite.ino b/examples/NonBlockingWrite/NonBlockingWrite.ino index 017ec59..d282826 100644 --- a/examples/NonBlockingWrite/NonBlockingWrite.ino +++ b/examples/NonBlockingWrite/NonBlockingWrite.ino @@ -1,25 +1,25 @@ /**************************************************************************************************************************** NonBlockingWrite.ino - + For all RP2040 boads using Arduimo-mbed or arduino-pico core - + RP2040_SD is a library enable the usage of SD on RP2040-based boards - + This Library 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 Library 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 the Arduino SdFat Library. + You should have received a copy of the GNU General Public License along with the Arduino SdFat Library. If not, see . - + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - + (C) Copyright 2009 by William Greiman (C) Copyright 2010 SparkFun Electronics (C) Copyright 2021 by Khoi Hoang - + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD Licensed under GPL-3.0 license *****************************************************************************************************************************/ @@ -48,7 +48,7 @@ #endif #if defined(ARDUINO_ARCH_MBED) - + #define PIN_SD_MOSI PIN_SPI_MOSI #define PIN_SD_MISO PIN_SPI_MISO #define PIN_SD_SCK PIN_SPI_SCK @@ -60,7 +60,7 @@ #define PIN_SD_MISO PIN_SPI0_MISO #define PIN_SD_SCK PIN_SPI0_SCK #define PIN_SD_SS PIN_SPI0_SS - + #endif #define _RP2040_SD_LOGLEVEL_ 4 @@ -79,10 +79,11 @@ String buffer; unsigned long lastMillis = 0; -void setup() +void setup() { // Open serial communications and wait for port to open: Serial.begin(115200); + while (!Serial); delay(1000); @@ -92,21 +93,25 @@ void setup() #else Serial.print("Starting SD Card NonBlockingWrite on "); #endif - + Serial.println(BOARD_NAME); Serial.println(RP2040_SD_VERSION); - - Serial.print("Initializing SD card with SS = "); Serial.println(PIN_SD_SS); - Serial.print("SCK = "); Serial.println(PIN_SD_SCK); - Serial.print("MOSI = "); Serial.println(PIN_SD_MOSI); - Serial.print("MISO = "); Serial.println(PIN_SD_MISO); - if (!SD.begin(PIN_SD_SS)) + Serial.print("Initializing SD card with SS = "); + Serial.println(PIN_SD_SS); + Serial.print("SCK = "); + Serial.println(PIN_SD_SCK); + Serial.print("MOSI = "); + Serial.println(PIN_SD_MOSI); + Serial.print("MISO = "); + Serial.println(PIN_SD_MISO); + + if (!SD.begin(PIN_SD_SS)) { Serial.println("Initialization failed!"); return; } - + Serial.println("Initialization done."); // reserve 1kB for String used as a buffer @@ -116,9 +121,10 @@ void setup() pinMode(LED_BUILTIN, OUTPUT); // init the SD card - if (!SD.begin()) + if (!SD.begin()) { Serial.println("Card failed, or not present"); + // don't do anything more: while (1); } @@ -129,10 +135,12 @@ void setup() // try to open the file for writing txtFile = SD.open(filename, FILE_WRITE); - - if (!txtFile) + + if (!txtFile) { - Serial.print("error opening "); Serial.println(filename); + Serial.print("error opening "); + Serial.println(filename); + while (1); } @@ -141,12 +149,12 @@ void setup() txtFile.println("Hello World!"); } -void loop() +void loop() { // check if it's been over 100 ms since the last line added unsigned long now = millis(); - - if ((now - lastMillis) >= 100) + + if ((now - lastMillis) >= 100) { // add a new line to the buffer buffer += "Hello "; @@ -159,8 +167,8 @@ void loop() // check if the SD card is available to write data without blocking // and if the buffered data is enough for the full chunk size unsigned int chunkSize = txtFile.availableForWrite(); - - if (chunkSize && buffer.length() >= chunkSize) + + if (chunkSize && buffer.length() >= chunkSize) { // write to file and blink LED digitalWrite(LED_BUILTIN, HIGH); diff --git a/examples/ReadWrite/ReadWrite.ino b/examples/ReadWrite/ReadWrite.ino index 0cf59eb..2a32b79 100644 --- a/examples/ReadWrite/ReadWrite.ino +++ b/examples/ReadWrite/ReadWrite.ino @@ -1,25 +1,25 @@ /**************************************************************************************************************************** ReadWrite.ino - + For all RP2040 boads using Arduimo-mbed or arduino-pico core - + RP2040_SD is a library enable the usage of SD on RP2040-based boards - + This Library 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 Library 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 the Arduino SdFat Library. + You should have received a copy of the GNU General Public License along with the Arduino SdFat Library. If not, see . - + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - + (C) Copyright 2009 by William Greiman (C) Copyright 2010 SparkFun Electronics (C) Copyright 2021 by Khoi Hoang - + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD Licensed under GPL-3.0 license *****************************************************************************************************************************/ @@ -48,7 +48,7 @@ #endif #if defined(ARDUINO_ARCH_MBED) - + #define PIN_SD_MOSI PIN_SPI_MOSI #define PIN_SD_MISO PIN_SPI_MISO #define PIN_SD_SCK PIN_SPI_SCK @@ -60,7 +60,7 @@ #define PIN_SD_MISO PIN_SPI0_MISO #define PIN_SD_SCK PIN_SPI0_SCK #define PIN_SD_SS PIN_SPI0_SS - + #endif #define _RP2040_SD_LOGLEVEL_ 4 @@ -70,10 +70,11 @@ File myFile; -void setup() +void setup() { // Open serial communications and wait for port to open: Serial.begin(115200); + while (!Serial); delay(1000); @@ -83,58 +84,66 @@ void setup() #else Serial.print("Starting SD Card ReadWrite on "); #endif - + Serial.println(BOARD_NAME); Serial.println(RP2040_SD_VERSION); - - Serial.print("Initializing SD card with SS = "); Serial.println(PIN_SD_SS); - Serial.print("SCK = "); Serial.println(PIN_SD_SCK); - Serial.print("MOSI = "); Serial.println(PIN_SD_MOSI); - Serial.print("MISO = "); Serial.println(PIN_SD_MISO); - if (!SD.begin(PIN_SD_SS)) + Serial.print("Initializing SD card with SS = "); + Serial.println(PIN_SD_SS); + Serial.print("SCK = "); + Serial.println(PIN_SD_SCK); + Serial.print("MOSI = "); + Serial.println(PIN_SD_MOSI); + Serial.print("MISO = "); + Serial.println(PIN_SD_MISO); + + if (!SD.begin(PIN_SD_SS)) { Serial.println("Initialization failed!"); return; } - + Serial.println("Initialization done."); - #define fileName "newtest0.txt" +#define fileName "newtest0.txt" char writeData[] = "Testing writing to " fileName; - + // open the file. note that only one file can be open at a time, // so you have to close this one before opening another. myFile = SD.open(fileName, FILE_WRITE); // if the file opened okay, write to it: - if (myFile) + if (myFile) { - Serial.print("Writing to "); Serial.print(fileName); - Serial.print(" ==> "); Serial.println(writeData); + Serial.print("Writing to "); + Serial.print(fileName); + Serial.print(" ==> "); + Serial.println(writeData); myFile.println(writeData); - + // close the file: myFile.close(); Serial.println("done."); - } - else + } + else { // if the file didn't open, print an error: - Serial.print("Error opening "); Serial.println(fileName); + Serial.print("Error opening "); + Serial.println(fileName); } // re-open the file for reading: myFile = SD.open(fileName, FILE_READ); - - if (myFile) + + if (myFile) { - Serial.print("Reading from "); Serial.println(fileName); + Serial.print("Reading from "); + Serial.println(fileName); Serial.println("==============="); // read from the file until there's nothing else in it: - while (myFile.available()) + while (myFile.available()) { Serial.write(myFile.read()); } @@ -143,15 +152,16 @@ void setup() myFile.close(); Serial.println("==============="); - } - else + } + else { // if the file didn't open, print an error: - Serial.print("Error opening "); Serial.println(fileName); + Serial.print("Error opening "); + Serial.println(fileName); } } -void loop() +void loop() { // nothing happens after setup } diff --git a/library.json b/library.json index cfaf65d..4986455 100644 --- a/library.json +++ b/library.json @@ -2,7 +2,7 @@ "name": "RP2040_SD", "version": "1.0.1", "keywords": "data-storage, sd, sd-card, fat, fat16, fat32, sd-fat, ex-fat, spi, mbed, rpi-pico, rp2040, nano-rp2040-connect", - "description": "This library enables you to use SPI SD cards (FAT16, FAT32, exFAT) with RP2040-based boards such as Nano_RP2040_Connect, RASPBERRY_PI_PICO using either RP2040 Arduino-mbed or arduino-pico core.. This SD-Fat v2 can support FAT16, FAT32, exFAT. exFAT supports files larger than 4GB by using uint64_t as file offset.", + "description": "This library enables you to use SPI SD cards (FAT16, FAT32, exFAT) with RP2040-based boards such as Nano_RP2040_Connect, RASPBERRY_PI_PICO using either RP2040 Arduino-mbed or arduino-pico core. This SD-Fat v2 can support FAT16, FAT32, exFAT. exFAT supports files larger than 4GB by using uint64_t as file offset.", "authors": [ { "name": "Bill Greiman", @@ -29,8 +29,10 @@ "tests" ] }, + "license": "MIT", "frameworks": "*", "platforms": ["raspberrypi"], "examples": "examples/*/*/*.ino", - "license": "MIT" + "headers": ["RP2040_SD.h"] + } diff --git a/library.properties b/library.properties index 4f47cb1..1528e51 100644 --- a/library.properties +++ b/library.properties @@ -8,5 +8,5 @@ category=Data Storage url=https://github.com/khoih-prog/RP2040_SD architectures=rp2040, mbed_rp2040, mbed_nano repository=https://github.com/khoih-prog/RP2040_SD -includes=RP2040_SD.h license=MIT +includes=RP2040_SD.h diff --git a/src/RP2040_SD.h b/src/RP2040_SD.h index 6645f6b..8a8b502 100644 --- a/src/RP2040_SD.h +++ b/src/RP2040_SD.h @@ -1,30 +1,30 @@ /**************************************************************************************************************************** RP2040_SD.h - a slightly more friendly wrapper for sdfatlib - + For all RP2040 boads using Arduimo-mbed or arduino-pico core - + RP2040_SD is a library enable the usage of SD on RP2040-based boards - + This Library 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 Library 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 the Arduino SdFat Library. + You should have received a copy of the GNU General Public License along with the Arduino SdFat Library. If not, see . - + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - + (C) Copyright 2009 by William Greiman (C) Copyright 2010 SparkFun Electronics (C) Copyright 2021 by Khoi Hoang - + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD Licensed under GPL-3.0 license - + Version: 1.0.1 - + Version Modified By Date Comments ------- ----------- ---------- ----------- 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core @@ -56,124 +56,124 @@ #define FILE_READ O_READ #define FILE_WRITE (O_READ | O_WRITE | O_CREAT | O_APPEND) -namespace RP2040_SDLib +namespace RP2040_SDLib { - class File : public Stream - { - private: - - char _name[13]; // our name - RP2040_SdFile *_file; // underlying file pointer - - //for debugging file open/close leaks - uint8_t nfilecount = 0; - - public: - - File(RP2040_SdFile f, const char *name); // wraps an underlying RP2040_SdFile - File(void); // 'empty' constructor - - virtual size_t write(uint8_t); - virtual size_t write(const uint8_t *buf, size_t size); - virtual int availableForWrite(); - virtual int read(); - virtual int peek(); - virtual int available(); - virtual void flush(); - int read(void *buf, uint16_t nbyte); - bool seek(uint32_t pos); - uint32_t position(); - uint32_t size(); - void close(); - operator bool(); - char * name(); - - bool isDirectory(void); - File openNextFile(uint8_t mode = O_RDONLY); - void rewindDirectory(void); - - using Print::write; - }; - - class SDClass - { - private: - - // These are required for initialisation and use of sdfatlib - Sd2Card card; - RP2040_SdVolume volume; - RP2040_SdFile root; - - // my quick&dirty iterator, should be replaced - RP2040_SdFile getParentDir(const char *filepath, int *indx); - - public: - - // This needs to be called to set up the connection to the SD card - // before other methods are used. - bool begin(uint8_t csPin = SD_CHIP_SELECT_PIN); - bool begin(uint32_t clock, uint8_t csPin); - - //call this when a card is removed. It will allow you to insert and initialise a new card. - void end(); - - // Open the specified file/directory with the supplied mode (e.g. read or - // write, etc). Returns a File object for interacting with the file. - // Note that currently only one file can be open at a time. - File open(const char *filename, uint8_t mode = FILE_READ); - - File open(const String &filename, uint8_t mode = FILE_READ) - { - return open(filename.c_str(), mode); - } - - // Methods to determine if the requested file path exists. - bool exists(const char *filepath); - - bool exists(const String &filepath) - { - return exists(filepath.c_str()); - } - - // Create the requested directory heirarchy--if intermediate directories - // do not exist they will be created. - bool mkdir(const char *filepath); - - bool mkdir(const String &filepath) - { - return mkdir(filepath.c_str()); - } - - // Delete the file. - bool remove(const char *filepath); - - bool remove(const String &filepath) - { - return remove(filepath.c_str()); - } - - bool rmdir(const char *filepath); - - bool rmdir(const String &filepath) - { - return rmdir(filepath.c_str()); - } - - private: - - // This is used to determine the mode used to open a file - // it's here because it's the easiest place to pass the - // information through the directory walking function. But - // it's probably not the best place for it. - // It shouldn't be set directly--it is set via the parameters to `open`. - int fileOpenMode; - - friend class File; - friend bool callback_openPath(RP2040_SdFile&, const char *, bool, void *); - }; - - //extern SDClass SD; - static SDClass SD; +class File : public Stream +{ + private: + + char _name[13]; // our name + RP2040_SdFile *_file; // underlying file pointer + + //for debugging file open/close leaks + uint8_t nfilecount = 0; + + public: + + File(RP2040_SdFile f, const char *name); // wraps an underlying RP2040_SdFile + File(void); // 'empty' constructor + + virtual size_t write(uint8_t); + virtual size_t write(const uint8_t *buf, size_t size); + virtual int availableForWrite(); + virtual int read(); + virtual int peek(); + virtual int available(); + virtual void flush(); + int read(void *buf, uint16_t nbyte); + bool seek(uint32_t pos); + uint32_t position(); + uint32_t size(); + void close(); + operator bool(); + char * name(); + + bool isDirectory(void); + File openNextFile(uint8_t mode = O_RDONLY); + void rewindDirectory(void); + + using Print::write; +}; + +class SDClass +{ + private: + + // These are required for initialisation and use of sdfatlib + Sd2Card card; + RP2040_SdVolume volume; + RP2040_SdFile root; + + // my quick&dirty iterator, should be replaced + RP2040_SdFile getParentDir(const char *filepath, int *indx); + + public: + + // This needs to be called to set up the connection to the SD card + // before other methods are used. + bool begin(uint8_t csPin = SD_CHIP_SELECT_PIN); + bool begin(uint32_t clock, uint8_t csPin); + + //call this when a card is removed. It will allow you to insert and initialise a new card. + void end(); + + // Open the specified file/directory with the supplied mode (e.g. read or + // write, etc). Returns a File object for interacting with the file. + // Note that currently only one file can be open at a time. + File open(const char *filename, uint8_t mode = FILE_READ); + + File open(const String &filename, uint8_t mode = FILE_READ) + { + return open(filename.c_str(), mode); + } + + // Methods to determine if the requested file path exists. + bool exists(const char *filepath); + + bool exists(const String &filepath) + { + return exists(filepath.c_str()); + } + + // Create the requested directory heirarchy--if intermediate directories + // do not exist they will be created. + bool mkdir(const char *filepath); + + bool mkdir(const String &filepath) + { + return mkdir(filepath.c_str()); + } + + // Delete the file. + bool remove(const char *filepath); + + bool remove(const String &filepath) + { + return remove(filepath.c_str()); + } + + bool rmdir(const char *filepath); + + bool rmdir(const String &filepath) + { + return rmdir(filepath.c_str()); + } + + private: + + // This is used to determine the mode used to open a file + // it's here because it's the easiest place to pass the + // information through the directory walking function. But + // it's probably not the best place for it. + // It shouldn't be set directly--it is set via the parameters to `open`. + int fileOpenMode; + + friend class File; + friend bool callback_openPath(RP2040_SdFile&, const char *, bool, void *); +}; + +//extern SDClass SD; +static SDClass SD; }; // We enclose File and SD classes in namespace RP2040_SDLib to avoid conflicts diff --git a/src/utility/FatStructs.h b/src/utility/FatStructs.h index c3894f1..8291b36 100644 --- a/src/utility/FatStructs.h +++ b/src/utility/FatStructs.h @@ -1,458 +1,458 @@ -/**************************************************************************************************************************** - FatStructs.h - - For all RP2040 boads using Arduimo-mbed or arduino-pico core - - RP2040_SD is a library enable the usage of SD on RP2040-based boards - - This Library 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 Library 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 the Arduino SdFat Library. - If not, see . - - Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - - (C) Copyright 2009 by William Greiman - (C) Copyright 2010 SparkFun Electronics - (C) Copyright 2021 by Khoi Hoang - - Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD - Licensed under GPL-3.0 license - - Version: 1.0.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core - 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO - *****************************************************************************************************************************/ - -#pragma once - -#ifndef FatStructs_h -#define FatStructs_h - -/** - \file - FAT file structures -*/ -/* - mostly from Microsoft document fatgen103.doc - http://www.microsoft.com/whdc/system/platform/firmware/fatgen.mspx -*/ -//------------------------------------------------------------------------------ -/** Value for byte 510 of boot block or MBR */ -#define BOOTSIG0 0x55 - -/** Value for byte 511 of boot block or MBR */ -#define BOOTSIG1 0xAA -//------------------------------------------------------------------------------ -/** - \struct partitionTable - \brief MBR partition table entry - - A partition table entry for a MBR formatted storage device. - The MBR partition table has four entries. -*/ -struct partitionTable -{ - /** - Boot Indicator . Indicates whether the volume is the active - partition. Legal values include: 0X00. Do not use for booting. - 0X80 Active partition. - */ - uint8_t boot; - /** - Head part of Cylinder-head-sector address of the first block in - the partition. Legal values are 0-255. Only used in old PC BIOS. - */ - uint8_t beginHead; - /** - Sector part of Cylinder-head-sector address of the first block in - the partition. Legal values are 1-63. Only used in old PC BIOS. - */ - unsigned beginSector : 6; - /** High bits cylinder for first block in partition. */ - unsigned beginCylinderHigh : 2; - /** - Combine beginCylinderLow with beginCylinderHigh. Legal values - are 0-1023. Only used in old PC BIOS. - */ - uint8_t beginCylinderLow; - /** - Partition type. See defines that begin with PART_TYPE_ for - some Microsoft partition types. - */ - uint8_t type; - /** - head part of cylinder-head-sector address of the last sector in the - partition. Legal values are 0-255. Only used in old PC BIOS. - */ - uint8_t endHead; - /** - Sector part of cylinder-head-sector address of the last sector in - the partition. Legal values are 1-63. Only used in old PC BIOS. - */ - unsigned endSector : 6; - /** High bits of end cylinder */ - unsigned endCylinderHigh : 2; - /** - Combine endCylinderLow with endCylinderHigh. Legal values - are 0-1023. Only used in old PC BIOS. - */ - uint8_t endCylinderLow; - /** Logical block address of the first block in the partition. */ - uint32_t firstSector; - /** Length of the partition, in blocks. */ - uint32_t totalSectors; -} __attribute__((packed)); - -/** Type name for partitionTable */ -typedef struct partitionTable part_t; -//------------------------------------------------------------------------------ -/** - \struct masterBootRecord - - \brief Master Boot Record - - The first block of a storage device that is formatted with a MBR. -*/ -struct masterBootRecord -{ - /** Code Area for master boot program. */ - uint8_t codeArea[440]; - /** Optional WindowsNT disk signature. May contain more boot code. */ - uint32_t diskSignature; - /** Usually zero but may be more boot code. */ - uint16_t usuallyZero; - /** Partition tables. */ - part_t part[4]; - /** First MBR signature byte. Must be 0X55 */ - uint8_t mbrSig0; - /** Second MBR signature byte. Must be 0XAA */ - uint8_t mbrSig1; -} __attribute__((packed)); - -/** Type name for masterBootRecord */ -typedef struct masterBootRecord mbr_t; - -//------------------------------------------------------------------------------ -/** - \struct biosParmBlock - - \brief BIOS parameter block - - The BIOS parameter block describes the physical layout of a FAT volume. -*/ -struct biosParmBlock -{ - /** - Count of bytes per sector. This value may take on only the - following values: 512, 1024, 2048 or 4096 - */ - uint16_t bytesPerSector; - /** - Number of sectors per allocation unit. This value must be a - power of 2 that is greater than 0. The legal values are - 1, 2, 4, 8, 16, 32, 64, and 128. - */ - uint8_t sectorsPerCluster; - /** - Number of sectors before the first FAT. - This value must not be zero. - */ - uint16_t reservedSectorCount; - /** The count of FAT data structures on the volume. This field should - always contain the value 2 for any FAT volume of any type. - */ - uint8_t fatCount; - /** - For FAT12 and FAT16 volumes, this field contains the count of - 32-byte directory entries in the root directory. For FAT32 volumes, - this field must be set to 0. For FAT12 and FAT16 volumes, this - value should always specify a count that when multiplied by 32 - results in a multiple of bytesPerSector. FAT16 volumes should - use the value 512. - */ - uint16_t rootDirEntryCount; - /** - This field is the old 16-bit total count of sectors on the volume. - This count includes the count of all sectors in all four regions - of the volume. This field can be 0; if it is 0, then totalSectors32 - must be non-zero. For FAT32 volumes, this field must be 0. For - FAT12 and FAT16 volumes, this field contains the sector count, and - totalSectors32 is 0 if the total sector count fits - (is less than 0x10000). - */ - uint16_t totalSectors16; - /** - This dates back to the old MS-DOS 1.x media determination and is - no longer usually used for anything. 0xF8 is the standard value - for fixed (non-removable) media. For removable media, 0xF0 is - frequently used. Legal values are 0xF0 or 0xF8-0xFF. - */ - uint8_t mediaType; - /** - Count of sectors occupied by one FAT on FAT12/FAT16 volumes. - On FAT32 volumes this field must be 0, and sectorsPerFat32 - contains the FAT size count. - */ - uint16_t sectorsPerFat16; - /** Sectors per track for interrupt 0x13. Not used otherwise. */ - uint16_t sectorsPerTrtack; - /** Number of heads for interrupt 0x13. Not used otherwise. */ - uint16_t headCount; - /** - Count of hidden sectors preceding the partition that contains this - FAT volume. This field is generally only relevant for media - visible on interrupt 0x13. - */ - uint32_t hidddenSectors; - /** - This field is the new 32-bit total count of sectors on the volume. - This count includes the count of all sectors in all four regions - of the volume. This field can be 0; if it is 0, then - totalSectors16 must be non-zero. - */ - uint32_t totalSectors32; - /** - Count of sectors occupied by one FAT on FAT32 volumes. - */ - uint32_t sectorsPerFat32; - /** - This field is only defined for FAT32 media and does not exist on - FAT12 and FAT16 media. - Bits 0-3 -- Zero-based number of active FAT. - Only valid if mirroring is disabled. - Bits 4-6 -- Reserved. - Bit 7 -- 0 means the FAT is mirrored at runtime into all FATs. - -- 1 means only one FAT is active; it is the one referenced in bits 0-3. - Bits 8-15 -- Reserved. - */ - uint16_t fat32Flags; - /** - FAT32 version. High byte is major revision number. - Low byte is minor revision number. Only 0.0 define. - */ - uint16_t fat32Version; - /** - Cluster number of the first cluster of the root directory for FAT32. - This usually 2 but not required to be 2. - */ - uint32_t fat32RootCluster; - /** - Sector number of FSINFO structure in the reserved area of the - FAT32 volume. Usually 1. - */ - uint16_t fat32FSInfo; - /** - If non-zero, indicates the sector number in the reserved area - of the volume of a copy of the boot record. Usually 6. - No value other than 6 is recommended. - */ - uint16_t fat32BackBootBlock; - /** - Reserved for future expansion. Code that formats FAT32 volumes - should always set all of the bytes of this field to 0. - */ - uint8_t fat32Reserved[12]; -} __attribute__((packed)); - -/** Type name for biosParmBlock */ -typedef struct biosParmBlock bpb_t; - -//------------------------------------------------------------------------------ -/** - \struct fat32BootSector - - \brief Boot sector for a FAT16 or FAT32 volume. - -*/ -struct fat32BootSector -{ - /** X86 jmp to boot program */ - uint8_t jmpToBootCode[3]; - /** informational only - don't depend on it */ - char oemName[8]; - /** BIOS Parameter Block */ - bpb_t bpb; - /** for int0x13 use value 0X80 for hard drive */ - uint8_t driveNumber; - /** used by Windows NT - should be zero for FAT */ - uint8_t reserved1; - /** 0X29 if next three fields are valid */ - uint8_t bootSignature; - /** usually generated by combining date and time */ - uint32_t volumeSerialNumber; - /** should match volume label in root dir */ - char volumeLabel[11]; - /** informational only - don't depend on it */ - char fileSystemType[8]; - /** X86 boot code */ - uint8_t bootCode[420]; - /** must be 0X55 */ - uint8_t bootSectorSig0; - /** must be 0XAA */ - uint8_t bootSectorSig1; -} __attribute__((packed)); - -//------------------------------------------------------------------------------ -// End Of Chain values for FAT entries -/** FAT16 end of chain value used by Microsoft. */ -#define FAT16EOC (uint16_t) 0XFFFF - -/** Minimum value for FAT16 EOC. Use to test for EOC. */ -#define FAT16EOC_MIN (uint16_t) 0XFFF8 - -/** FAT32 end of chain value used by Microsoft. */ -#define FAT32EOC (uint32_t) 0x0FFFFFFF - -/** Minimum value for FAT32 EOC. Use to test for EOC. */ -#define FAT32EOC_MIN (uint32_t) 0x0FFFFFF8 - -/** Mask a for FAT32 entry. Entries are 28 bits. */ -#define FAT32MASK (uint32_t) 0x0FFFFFFF - -/** Type name for fat32BootSector */ -typedef struct fat32BootSector fbs_t; - -//------------------------------------------------------------------------------ -/** - \struct directoryEntry - \brief FAT short directory entry - - Short means short 8.3 name, not the entry size. - - Date Format. A FAT directory entry date stamp is a 16-bit field that is - basically a date relative to the MS-DOS epoch of 01/01/1980. Here is the - format (bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the - 16-bit word): - - Bits 9-15: Count of years from 1980, valid value range 0-127 - inclusive (1980-2107). - - Bits 5-8: Month of year, 1 = January, valid value range 1-12 inclusive. - - Bits 0-4: Day of month, valid value range 1-31 inclusive. - - Time Format. A FAT directory entry time stamp is a 16-bit field that has - a granularity of 2 seconds. Here is the format (bit 0 is the LSB of the - 16-bit word, bit 15 is the MSB of the 16-bit word). - - Bits 11-15: Hours, valid value range 0-23 inclusive. - - Bits 5-10: Minutes, valid value range 0-59 inclusive. - - Bits 0-4: 2-second count, valid value range 0-29 inclusive (0 - 58 seconds). - - The valid time range is from Midnight 00:00:00 to 23:59:58. -*/ -struct directoryEntry -{ - /** - Short 8.3 name. - The first eight bytes contain the file name with blank fill. - The last three bytes contain the file extension with blank fill. - */ - uint8_t name[11]; - /** Entry attributes. - - The upper two bits of the attribute byte are reserved and should - always be set to 0 when a file is created and never modified or - looked at after that. See defines that begin with DIR_ATT_. - */ - uint8_t attributes; - /** - Reserved for use by Windows NT. Set value to 0 when a file is - created and never modify or look at it after that. - */ - uint8_t reservedNT; - /** - The granularity of the seconds part of creationTime is 2 seconds - so this field is a count of tenths of a second and its valid - value range is 0-199 inclusive. (WHG note - seems to be hundredths) - */ - uint8_t creationTimeTenths; - /** Time file was created. */ - uint16_t creationTime; - /** Date file was created. */ - uint16_t creationDate; - /** - Last access date. Note that there is no last access time, only - a date. This is the date of last read or write. In the case of - a write, this should be set to the same date as lastWriteDate. - */ - uint16_t lastAccessDate; - /** - High word of this entry's first cluster number (always 0 for a - FAT12 or FAT16 volume). - */ - uint16_t firstClusterHigh; - /** Time of last write. File creation is considered a write. */ - uint16_t lastWriteTime; - /** Date of last write. File creation is considered a write. */ - uint16_t lastWriteDate; - /** Low word of this entry's first cluster number. */ - uint16_t firstClusterLow; - /** 32-bit unsigned holding this file's size in bytes. */ - uint32_t fileSize; -}; // __attribute__((packed)); - -//------------------------------------------------------------------------------ -// Definitions for directory entries -// -/** Type name for directoryEntry */ -typedef struct directoryEntry dir_t; - -enum -{ - DIR_NAME_0XE5 = 0X05, - DIR_NAME_DELETED = 0XE5, - DIR_NAME_FREE = 0X00, - DIR_ATT_READ_ONLY = 0X01, - DIR_ATT_HIDDEN = 0X02, - DIR_ATT_SYSTEM = 0X04, - DIR_ATT_VOLUME_ID = 0X08, - DIR_ATT_DIRECTORY = 0X10, - DIR_ATT_ARCHIVE = 0X20, - DIR_ATT_LONG_NAME = 0X0F, -}; - -/** Test mask for long name entry */ -#define DIR_ATT_LONG_NAME_MASK 0X3F - -/** defined attribute bits */ -#define DIR_ATT_DEFINED_BITS 0X3F - -/** Directory entry is part of a long name */ -static inline uint8_t DIR_IS_LONG_NAME(const dir_t* dir) -{ - return (dir->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME; -} - -/** Mask for file/subdirectory tests */ -uint8_t const DIR_ATT_FILE_TYPE_MASK = (DIR_ATT_VOLUME_ID | DIR_ATT_DIRECTORY); - -/** Directory entry is for a file */ -static inline uint8_t DIR_IS_FILE(const dir_t* dir) -{ - return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == 0; -} - -/** Directory entry is for a subdirectory */ -static inline uint8_t DIR_IS_SUBDIR(const dir_t* dir) -{ - return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == DIR_ATT_DIRECTORY; -} - -/** Directory entry is for a file or subdirectory */ -static inline uint8_t DIR_IS_FILE_OR_SUBDIR(const dir_t* dir) -{ - return (dir->attributes & DIR_ATT_VOLUME_ID) == 0; -} - -#endif // FatStructs_h +/**************************************************************************************************************************** + FatStructs.h + + For all RP2040 boads using Arduimo-mbed or arduino-pico core + + RP2040_SD is a library enable the usage of SD on RP2040-based boards + + This Library 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 Library 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 the Arduino SdFat Library. + If not, see . + + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) + + (C) Copyright 2009 by William Greiman + (C) Copyright 2010 SparkFun Electronics + (C) Copyright 2021 by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD + Licensed under GPL-3.0 license + + Version: 1.0.1 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core + 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO + *****************************************************************************************************************************/ + +#pragma once + +#ifndef FatStructs_h +#define FatStructs_h + +/** + \file + FAT file structures +*/ +/* + mostly from Microsoft document fatgen103.doc + http://www.microsoft.com/whdc/system/platform/firmware/fatgen.mspx +*/ +//------------------------------------------------------------------------------ +/** Value for byte 510 of boot block or MBR */ +#define BOOTSIG0 0x55 + +/** Value for byte 511 of boot block or MBR */ +#define BOOTSIG1 0xAA +//------------------------------------------------------------------------------ +/** + \struct partitionTable + \brief MBR partition table entry + + A partition table entry for a MBR formatted storage device. + The MBR partition table has four entries. +*/ +struct partitionTable +{ + /** + Boot Indicator . Indicates whether the volume is the active + partition. Legal values include: 0X00. Do not use for booting. + 0X80 Active partition. + */ + uint8_t boot; + /** + Head part of Cylinder-head-sector address of the first block in + the partition. Legal values are 0-255. Only used in old PC BIOS. + */ + uint8_t beginHead; + /** + Sector part of Cylinder-head-sector address of the first block in + the partition. Legal values are 1-63. Only used in old PC BIOS. + */ + unsigned beginSector : 6; + /** High bits cylinder for first block in partition. */ + unsigned beginCylinderHigh : 2; + /** + Combine beginCylinderLow with beginCylinderHigh. Legal values + are 0-1023. Only used in old PC BIOS. + */ + uint8_t beginCylinderLow; + /** + Partition type. See defines that begin with PART_TYPE_ for + some Microsoft partition types. + */ + uint8_t type; + /** + head part of cylinder-head-sector address of the last sector in the + partition. Legal values are 0-255. Only used in old PC BIOS. + */ + uint8_t endHead; + /** + Sector part of cylinder-head-sector address of the last sector in + the partition. Legal values are 1-63. Only used in old PC BIOS. + */ + unsigned endSector : 6; + /** High bits of end cylinder */ + unsigned endCylinderHigh : 2; + /** + Combine endCylinderLow with endCylinderHigh. Legal values + are 0-1023. Only used in old PC BIOS. + */ + uint8_t endCylinderLow; + /** Logical block address of the first block in the partition. */ + uint32_t firstSector; + /** Length of the partition, in blocks. */ + uint32_t totalSectors; +} __attribute__((packed)); + +/** Type name for partitionTable */ +typedef struct partitionTable part_t; +//------------------------------------------------------------------------------ +/** + \struct masterBootRecord + + \brief Master Boot Record + + The first block of a storage device that is formatted with a MBR. +*/ +struct masterBootRecord +{ + /** Code Area for master boot program. */ + uint8_t codeArea[440]; + /** Optional WindowsNT disk signature. May contain more boot code. */ + uint32_t diskSignature; + /** Usually zero but may be more boot code. */ + uint16_t usuallyZero; + /** Partition tables. */ + part_t part[4]; + /** First MBR signature byte. Must be 0X55 */ + uint8_t mbrSig0; + /** Second MBR signature byte. Must be 0XAA */ + uint8_t mbrSig1; +} __attribute__((packed)); + +/** Type name for masterBootRecord */ +typedef struct masterBootRecord mbr_t; + +//------------------------------------------------------------------------------ +/** + \struct biosParmBlock + + \brief BIOS parameter block + + The BIOS parameter block describes the physical layout of a FAT volume. +*/ +struct biosParmBlock +{ + /** + Count of bytes per sector. This value may take on only the + following values: 512, 1024, 2048 or 4096 + */ + uint16_t bytesPerSector; + /** + Number of sectors per allocation unit. This value must be a + power of 2 that is greater than 0. The legal values are + 1, 2, 4, 8, 16, 32, 64, and 128. + */ + uint8_t sectorsPerCluster; + /** + Number of sectors before the first FAT. + This value must not be zero. + */ + uint16_t reservedSectorCount; + /** The count of FAT data structures on the volume. This field should + always contain the value 2 for any FAT volume of any type. + */ + uint8_t fatCount; + /** + For FAT12 and FAT16 volumes, this field contains the count of + 32-byte directory entries in the root directory. For FAT32 volumes, + this field must be set to 0. For FAT12 and FAT16 volumes, this + value should always specify a count that when multiplied by 32 + results in a multiple of bytesPerSector. FAT16 volumes should + use the value 512. + */ + uint16_t rootDirEntryCount; + /** + This field is the old 16-bit total count of sectors on the volume. + This count includes the count of all sectors in all four regions + of the volume. This field can be 0; if it is 0, then totalSectors32 + must be non-zero. For FAT32 volumes, this field must be 0. For + FAT12 and FAT16 volumes, this field contains the sector count, and + totalSectors32 is 0 if the total sector count fits + (is less than 0x10000). + */ + uint16_t totalSectors16; + /** + This dates back to the old MS-DOS 1.x media determination and is + no longer usually used for anything. 0xF8 is the standard value + for fixed (non-removable) media. For removable media, 0xF0 is + frequently used. Legal values are 0xF0 or 0xF8-0xFF. + */ + uint8_t mediaType; + /** + Count of sectors occupied by one FAT on FAT12/FAT16 volumes. + On FAT32 volumes this field must be 0, and sectorsPerFat32 + contains the FAT size count. + */ + uint16_t sectorsPerFat16; + /** Sectors per track for interrupt 0x13. Not used otherwise. */ + uint16_t sectorsPerTrtack; + /** Number of heads for interrupt 0x13. Not used otherwise. */ + uint16_t headCount; + /** + Count of hidden sectors preceding the partition that contains this + FAT volume. This field is generally only relevant for media + visible on interrupt 0x13. + */ + uint32_t hidddenSectors; + /** + This field is the new 32-bit total count of sectors on the volume. + This count includes the count of all sectors in all four regions + of the volume. This field can be 0; if it is 0, then + totalSectors16 must be non-zero. + */ + uint32_t totalSectors32; + /** + Count of sectors occupied by one FAT on FAT32 volumes. + */ + uint32_t sectorsPerFat32; + /** + This field is only defined for FAT32 media and does not exist on + FAT12 and FAT16 media. + Bits 0-3 -- Zero-based number of active FAT. + Only valid if mirroring is disabled. + Bits 4-6 -- Reserved. + Bit 7 -- 0 means the FAT is mirrored at runtime into all FATs. + -- 1 means only one FAT is active; it is the one referenced in bits 0-3. + Bits 8-15 -- Reserved. + */ + uint16_t fat32Flags; + /** + FAT32 version. High byte is major revision number. + Low byte is minor revision number. Only 0.0 define. + */ + uint16_t fat32Version; + /** + Cluster number of the first cluster of the root directory for FAT32. + This usually 2 but not required to be 2. + */ + uint32_t fat32RootCluster; + /** + Sector number of FSINFO structure in the reserved area of the + FAT32 volume. Usually 1. + */ + uint16_t fat32FSInfo; + /** + If non-zero, indicates the sector number in the reserved area + of the volume of a copy of the boot record. Usually 6. + No value other than 6 is recommended. + */ + uint16_t fat32BackBootBlock; + /** + Reserved for future expansion. Code that formats FAT32 volumes + should always set all of the bytes of this field to 0. + */ + uint8_t fat32Reserved[12]; +} __attribute__((packed)); + +/** Type name for biosParmBlock */ +typedef struct biosParmBlock bpb_t; + +//------------------------------------------------------------------------------ +/** + \struct fat32BootSector + + \brief Boot sector for a FAT16 or FAT32 volume. + +*/ +struct fat32BootSector +{ + /** X86 jmp to boot program */ + uint8_t jmpToBootCode[3]; + /** informational only - don't depend on it */ + char oemName[8]; + /** BIOS Parameter Block */ + bpb_t bpb; + /** for int0x13 use value 0X80 for hard drive */ + uint8_t driveNumber; + /** used by Windows NT - should be zero for FAT */ + uint8_t reserved1; + /** 0X29 if next three fields are valid */ + uint8_t bootSignature; + /** usually generated by combining date and time */ + uint32_t volumeSerialNumber; + /** should match volume label in root dir */ + char volumeLabel[11]; + /** informational only - don't depend on it */ + char fileSystemType[8]; + /** X86 boot code */ + uint8_t bootCode[420]; + /** must be 0X55 */ + uint8_t bootSectorSig0; + /** must be 0XAA */ + uint8_t bootSectorSig1; +} __attribute__((packed)); + +//------------------------------------------------------------------------------ +// End Of Chain values for FAT entries +/** FAT16 end of chain value used by Microsoft. */ +#define FAT16EOC (uint16_t) 0XFFFF + +/** Minimum value for FAT16 EOC. Use to test for EOC. */ +#define FAT16EOC_MIN (uint16_t) 0XFFF8 + +/** FAT32 end of chain value used by Microsoft. */ +#define FAT32EOC (uint32_t) 0x0FFFFFFF + +/** Minimum value for FAT32 EOC. Use to test for EOC. */ +#define FAT32EOC_MIN (uint32_t) 0x0FFFFFF8 + +/** Mask a for FAT32 entry. Entries are 28 bits. */ +#define FAT32MASK (uint32_t) 0x0FFFFFFF + +/** Type name for fat32BootSector */ +typedef struct fat32BootSector fbs_t; + +//------------------------------------------------------------------------------ +/** + \struct directoryEntry + \brief FAT short directory entry + + Short means short 8.3 name, not the entry size. + + Date Format. A FAT directory entry date stamp is a 16-bit field that is + basically a date relative to the MS-DOS epoch of 01/01/1980. Here is the + format (bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the + 16-bit word): + + Bits 9-15: Count of years from 1980, valid value range 0-127 + inclusive (1980-2107). + + Bits 5-8: Month of year, 1 = January, valid value range 1-12 inclusive. + + Bits 0-4: Day of month, valid value range 1-31 inclusive. + + Time Format. A FAT directory entry time stamp is a 16-bit field that has + a granularity of 2 seconds. Here is the format (bit 0 is the LSB of the + 16-bit word, bit 15 is the MSB of the 16-bit word). + + Bits 11-15: Hours, valid value range 0-23 inclusive. + + Bits 5-10: Minutes, valid value range 0-59 inclusive. + + Bits 0-4: 2-second count, valid value range 0-29 inclusive (0 - 58 seconds). + + The valid time range is from Midnight 00:00:00 to 23:59:58. +*/ +struct directoryEntry +{ + /** + Short 8.3 name. + The first eight bytes contain the file name with blank fill. + The last three bytes contain the file extension with blank fill. + */ + uint8_t name[11]; + /** Entry attributes. + + The upper two bits of the attribute byte are reserved and should + always be set to 0 when a file is created and never modified or + looked at after that. See defines that begin with DIR_ATT_. + */ + uint8_t attributes; + /** + Reserved for use by Windows NT. Set value to 0 when a file is + created and never modify or look at it after that. + */ + uint8_t reservedNT; + /** + The granularity of the seconds part of creationTime is 2 seconds + so this field is a count of tenths of a second and its valid + value range is 0-199 inclusive. (WHG note - seems to be hundredths) + */ + uint8_t creationTimeTenths; + /** Time file was created. */ + uint16_t creationTime; + /** Date file was created. */ + uint16_t creationDate; + /** + Last access date. Note that there is no last access time, only + a date. This is the date of last read or write. In the case of + a write, this should be set to the same date as lastWriteDate. + */ + uint16_t lastAccessDate; + /** + High word of this entry's first cluster number (always 0 for a + FAT12 or FAT16 volume). + */ + uint16_t firstClusterHigh; + /** Time of last write. File creation is considered a write. */ + uint16_t lastWriteTime; + /** Date of last write. File creation is considered a write. */ + uint16_t lastWriteDate; + /** Low word of this entry's first cluster number. */ + uint16_t firstClusterLow; + /** 32-bit unsigned holding this file's size in bytes. */ + uint32_t fileSize; +}; // __attribute__((packed)); + +//------------------------------------------------------------------------------ +// Definitions for directory entries +// +/** Type name for directoryEntry */ +typedef struct directoryEntry dir_t; + +enum +{ + DIR_NAME_0XE5 = 0X05, + DIR_NAME_DELETED = 0XE5, + DIR_NAME_FREE = 0X00, + DIR_ATT_READ_ONLY = 0X01, + DIR_ATT_HIDDEN = 0X02, + DIR_ATT_SYSTEM = 0X04, + DIR_ATT_VOLUME_ID = 0X08, + DIR_ATT_DIRECTORY = 0X10, + DIR_ATT_ARCHIVE = 0X20, + DIR_ATT_LONG_NAME = 0X0F, +}; + +/** Test mask for long name entry */ +#define DIR_ATT_LONG_NAME_MASK 0X3F + +/** defined attribute bits */ +#define DIR_ATT_DEFINED_BITS 0X3F + +/** Directory entry is part of a long name */ +static inline uint8_t DIR_IS_LONG_NAME(const dir_t* dir) +{ + return (dir->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME; +} + +/** Mask for file/subdirectory tests */ +uint8_t const DIR_ATT_FILE_TYPE_MASK = (DIR_ATT_VOLUME_ID | DIR_ATT_DIRECTORY); + +/** Directory entry is for a file */ +static inline uint8_t DIR_IS_FILE(const dir_t* dir) +{ + return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == 0; +} + +/** Directory entry is for a subdirectory */ +static inline uint8_t DIR_IS_SUBDIR(const dir_t* dir) +{ + return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == DIR_ATT_DIRECTORY; +} + +/** Directory entry is for a file or subdirectory */ +static inline uint8_t DIR_IS_FILE_OR_SUBDIR(const dir_t* dir) +{ + return (dir->attributes & DIR_ATT_VOLUME_ID) == 0; +} + +#endif // FatStructs_h diff --git a/src/utility/RP2040_SD_Debug.h b/src/utility/RP2040_SD_Debug.h index 1fe3e9f..ca0ad20 100644 --- a/src/utility/RP2040_SD_Debug.h +++ b/src/utility/RP2040_SD_Debug.h @@ -1,30 +1,30 @@ /**************************************************************************************************************************** RP2040_SD_Debug.h - + For all RP2040 boads using Arduimo-mbed or arduino-pico core - + RP2040_SD is a library enable the usage of SD on RP2040-based boards - + This Library 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 Library 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 the Arduino SdFat Library. + You should have received a copy of the GNU General Public License along with the Arduino SdFat Library. If not, see . - + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - + (C) Copyright 2009 by William Greiman (C) Copyright 2010 SparkFun Electronics (C) Copyright 2021 by Khoi Hoang - + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD Licensed under GPL-3.0 license - + Version: 1.0.1 - + Version Modified By Date Comments ------- ----------- ---------- ----------- 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core diff --git a/src/utility/Sd2Card.cpp b/src/utility/Sd2Card.cpp index 8cd9465..0ea83dd 100644 --- a/src/utility/Sd2Card.cpp +++ b/src/utility/Sd2Card.cpp @@ -1,993 +1,1025 @@ -/**************************************************************************************************************************** - Sd2Card.cpp - - For all RP2040 boads using Arduimo-mbed or arduino-pico core - - RP2040_SD is a library enable the usage of SD on RP2040-based boards - - This Library 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 Library 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 the Arduino SdFat Library. - If not, see . - - Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - - (C) Copyright 2009 by William Greiman - (C) Copyright 2010 SparkFun Electronics - (C) Copyright 2021 by Khoi Hoang - - Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD - Licensed under GPL-3.0 license - - Version: 1.0.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core - 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO - *****************************************************************************************************************************/ - -#define USE_SPI_LIB -#include -#include "Sd2Card.h" - -//------------------------------------------------------------------------------ - -#if ! (RP2040_SOFT_SPI) - - #ifdef USE_SPI_LIB - - #ifndef SDCARD_SPI - #define SDCARD_SPI SPI - #endif - - #include - static SPISettings settings; - - #endif - - // functions for hardware SPI - /** Send a byte to the card */ - static void spiSend(uint8_t b) - { - #ifndef USE_SPI_LIB - SPDR = b; - while (!(SPSR & (1 << SPIF))); - #else - SDCARD_SPI.transfer(b); - #endif - } - - /** Receive a byte from the card */ - static uint8_t spiRec() - { - #ifndef USE_SPI_LIB - spiSend(0XFF); - return SPDR; - #else - return SDCARD_SPI.transfer(0xFF); - #endif - } - -#else // SOFTWARE_SPI - - //------------------------------------------------------------------------------ - /** nop to tune soft SPI timing */ - #define nop asm volatile ("nop\n\t") - //------------------------------------------------------------------------------ - /** Soft SPI receive */ - uint8_t spiRec() - { - uint8_t data = 0; - // no interrupts during byte receive - about 8 us - cli(); - // output pin high - like sending 0XFF - fastDigitalWrite(SPI_MOSI_PIN, HIGH); - - for (uint8_t i = 0; i < 8; i++) - { - fastDigitalWrite(SPI_SCK_PIN, HIGH); - - // adjust so SCK is nice - nop; - nop; - - data <<= 1; - - if (fastDigitalRead(SPI_MISO_PIN)) - { - data |= 1; - } - - fastDigitalWrite(SPI_SCK_PIN, LOW); - } - - // enable interrupts - sei(); - - return data; - } - - //------------------------------------------------------------------------------ - /** Soft SPI send */ - void spiSend(uint8_t data) - { - // no interrupts during byte send - about 8 us - cli(); - - for (uint8_t i = 0; i < 8; i++) - { - fastDigitalWrite(SPI_SCK_PIN, LOW); - - fastDigitalWrite(SPI_MOSI_PIN, data & 0X80); - - data <<= 1; - - fastDigitalWrite(SPI_SCK_PIN, HIGH); - } - - // hold SCK high for a few ns - nop; - nop; - nop; - nop; - - fastDigitalWrite(SPI_SCK_PIN, LOW); - // enable interrupts - sei(); - } - -#endif // SOFTWARE_SPI - -//------------------------------------------------------------------------------ -// send command and return error code. Return zero for OK -uint8_t Sd2Card::cardCommand(uint8_t cmd, uint32_t arg) -{ - // end read if in partialBlockRead mode - readEnd(); - - // select card - chipSelectLow(); - - // wait up to 300 ms if busy - waitNotBusy(300); - - // send command - spiSend(cmd | 0x40); - - // send argument - for (int8_t s = 24; s >= 0; s -= 8) - { - spiSend(arg >> s); - } - - // send CRC - uint8_t crc = 0XFF; - - if (cmd == CMD0) - { - crc = 0X95; // correct crc for CMD0 with arg 0 - } - - if (cmd == CMD8) - { - crc = 0X87; // correct crc for CMD8 with arg 0X1AA - } - - spiSend(crc); - - // wait for response - for (uint8_t i = 0; ((status_ = spiRec()) & 0X80) && i != 0XFF; i++); - - return status_; -} - -//------------------------------------------------------------------------------ -/** - Determine the size of an SD flash memory card. - - \return The number of 512 byte data blocks in the card - or zero if an error occurs. -*/ -uint32_t Sd2Card::cardSize() -{ - csd_t csd; - - if (!readCSD(&csd)) - { - return 0; - } - - if (csd.v1.csd_ver == 0) - { - uint8_t read_bl_len = csd.v1.read_bl_len; - uint16_t c_size = (csd.v1.c_size_high << 10) | (csd.v1.c_size_mid << 2) | csd.v1.c_size_low; - uint8_t c_size_mult = (csd.v1.c_size_mult_high << 1) | csd.v1.c_size_mult_low; - - return (uint32_t)(c_size + 1) << (c_size_mult + read_bl_len - 7); - } - else if (csd.v2.csd_ver == 1) - { - uint32_t c_size = ((uint32_t)csd.v2.c_size_high << 16) | (csd.v2.c_size_mid << 8) | csd.v2.c_size_low; - - return (c_size + 1) << 10; - } - else - { - error(SD_CARD_ERROR_BAD_CSD); - - return 0; - } -} -//------------------------------------------------------------------------------ -static uint8_t chip_select_asserted = 0; - -void Sd2Card::chipSelectHigh() -{ - digitalWrite(chipSelectPin_, HIGH); - -#ifdef USE_SPI_LIB - if (chip_select_asserted) - { - chip_select_asserted = 0; - SDCARD_SPI.endTransaction(); - } -#endif -} -//------------------------------------------------------------------------------ -void Sd2Card::chipSelectLow() -{ -#ifdef USE_SPI_LIB - if (!chip_select_asserted) - { - chip_select_asserted = 1; - SDCARD_SPI.beginTransaction(settings); - } -#endif - - digitalWrite(chipSelectPin_, LOW); -} -//------------------------------------------------------------------------------ -/** Erase a range of blocks. - - \param[in] firstBlock The address of the first block in the range. - \param[in] lastBlock The address of the last block in the range. - - \note This function requests the SD card to do a flash erase for a - range of blocks. The data on the card after an erase operation is - either 0 or 1, depends on the card vendor. The card must support - single block erase. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. -*/ -uint8_t Sd2Card::erase(uint32_t firstBlock, uint32_t lastBlock) -{ - if (!eraseSingleBlockEnable()) - { - error(SD_CARD_ERROR_ERASE_SINGLE_BLOCK); - goto fail; - } - - if (type_ != SD_CARD_TYPE_SDHC) - { - firstBlock <<= 9; - lastBlock <<= 9; - } - - if (cardCommand(CMD32, firstBlock) || cardCommand(CMD33, lastBlock) || cardCommand(CMD38, 0)) - { - error(SD_CARD_ERROR_ERASE); - goto fail; - } - - if (!waitNotBusy(SD_ERASE_TIMEOUT)) - { - error(SD_CARD_ERROR_ERASE_TIMEOUT); - goto fail; - } - - chipSelectHigh(); - - return true; - -fail: - chipSelectHigh(); - - return false; -} -//------------------------------------------------------------------------------ -/** Determine if card supports single block erase. - - \return The value one, true, is returned if single block erase is supported. - The value zero, false, is returned if single block erase is not supported. -*/ -uint8_t Sd2Card::eraseSingleBlockEnable() -{ - csd_t csd; - return readCSD(&csd) ? csd.v1.erase_blk_en : 0; -} -//------------------------------------------------------------------------------ -/** - Initialize an SD flash memory card. - - \param[in] sckRateID SPI clock rate selector. See setSckRate(). - \param[in] chipSelectPin SD chip select pin number. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. The reason for failure - can be determined by calling errorCode() and errorData(). -*/ -uint8_t Sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin) -{ - errorCode_ = inBlock_ = partialBlockRead_ = type_ = 0; - chipSelectPin_ = chipSelectPin; - - // 16-bit init start time allows over a minute - unsigned int t0 = millis(); - uint32_t arg; - - // set pin modes - pinMode(chipSelectPin_, OUTPUT); - digitalWrite(chipSelectPin_, HIGH); - -#ifndef USE_SPI_LIB - pinMode(SPI_MISO_PIN, INPUT); - pinMode(SPI_MOSI_PIN, OUTPUT); - pinMode(SPI_SCK_PIN, OUTPUT); -#endif - -#ifndef SOFTWARE_SPI - #ifndef USE_SPI_LIB - // SS must be in output mode even it is not chip select - pinMode(SS_PIN, OUTPUT); - digitalWrite(SS_PIN, HIGH); // disable any SPI device using hardware SS pin - // Enable SPI, Master, clock rate f_osc/128 - SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0); - // clear double speed - SPSR &= ~(1 << SPI2X); - #else // USE_SPI_LIB - SDCARD_SPI.begin(); - settings = SPISettings(250000, MSBFIRST, SPI_MODE0); - #endif // USE_SPI_LIB -#endif // SOFTWARE_SPI - - // must supply min of 74 clock cycles with CS high. -#ifdef USE_SPI_LIB - SDCARD_SPI.beginTransaction(settings); -#endif - - for (uint8_t i = 0; i < 10; i++) - { - spiSend(0XFF); - } - -#ifdef USE_SPI_LIB - SDCARD_SPI.endTransaction(); -#endif - - chipSelectLow(); - - // command to go idle in SPI mode - while ((status_ = cardCommand(CMD0, 0)) != R1_IDLE_STATE) - { - unsigned int d = millis() - t0; - - if (d > SD_INIT_TIMEOUT) - { - error(SD_CARD_ERROR_CMD0); - goto fail; - } - } - - // check SD version - if ((cardCommand(CMD8, 0x1AA) & R1_ILLEGAL_COMMAND)) - { - type(SD_CARD_TYPE_SD1); - } - else - { - // only need last byte of r7 response - for (uint8_t i = 0; i < 4; i++) - { - status_ = spiRec(); - } - - if (status_ != 0XAA) - { - error(SD_CARD_ERROR_CMD8); - goto fail; - } - - type(SD_CARD_TYPE_SD2); - } - - // initialize card and send host supports SDHC if SD2 - arg = type() == SD_CARD_TYPE_SD2 ? 0X40000000 : 0; - - while ((status_ = cardAcmd(ACMD41, arg)) != R1_READY_STATE) - { - // check for timeout - unsigned int d = millis() - t0; - - if (d > SD_INIT_TIMEOUT) - { - error(SD_CARD_ERROR_ACMD41); - goto fail; - } - } - - // if SD2 read OCR register to check for SDHC card - if (type() == SD_CARD_TYPE_SD2) - { - if (cardCommand(CMD58, 0)) - { - error(SD_CARD_ERROR_CMD58); - goto fail; - } - - if ((spiRec() & 0XC0) == 0XC0) - { - type(SD_CARD_TYPE_SDHC); - } - - // discard rest of ocr - contains allowed voltage range - for (uint8_t i = 0; i < 3; i++) - { - spiRec(); - } - } - - chipSelectHigh(); - -#ifndef SOFTWARE_SPI - return setSckRate(sckRateID); -#else // SOFTWARE_SPI - return true; -#endif // SOFTWARE_SPI - -fail: - chipSelectHigh(); - return false; -} -//------------------------------------------------------------------------------ -/** - Enable or disable partial block reads. - - Enabling partial block reads improves performance by allowing a block - to be read over the SPI bus as several sub-blocks. Errors may occur - if the time between reads is too long since the SD card may timeout. - The SPI SS line will be held low until the entire block is read or - readEnd() is called. - - Use this for applications like the Adafruit Wave Shield. - - \param[in] value The value TRUE (non-zero) or FALSE (zero).) -*/ -void Sd2Card::partialBlockRead(uint8_t value) -{ - readEnd(); - partialBlockRead_ = value; -} -//------------------------------------------------------------------------------ -/** - Read a 512 byte block from an SD card device. - - \param[in] block Logical block to be read. - \param[out] dst Pointer to the location that will receive the data. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. -*/ -uint8_t Sd2Card::readBlock(uint32_t block, uint8_t* dst) -{ - return readData(block, 0, 512, dst); -} -//------------------------------------------------------------------------------ -/** - Read part of a 512 byte block from an SD card. - - \param[in] block Logical block to be read. - \param[in] offset Number of bytes to skip at start of block - \param[out] dst Pointer to the location that will receive the data. - \param[in] count Number of bytes to read - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. -*/ -uint8_t Sd2Card::readData(uint32_t block, uint16_t offset, uint16_t count, uint8_t* dst) -{ - if (count == 0) - { - return true; - } - - if ((count + offset) > 512) - { - goto fail; - } - - if (!inBlock_ || block != block_ || offset < offset_) - { - block_ = block; - - // use address if not SDHC card - if (type() != SD_CARD_TYPE_SDHC) - { - block <<= 9; - } - - if (cardCommand(CMD17, block)) - { - error(SD_CARD_ERROR_CMD17); - goto fail; - } - - if (!waitStartBlock()) - { - goto fail; - } - - offset_ = 0; - inBlock_ = 1; - } - -#ifdef OPTIMIZE_HARDWARE_SPI - // start first spi transfer - SPDR = 0XFF; - - // skip data before offset - for (; offset_ < offset; offset_++) - { - while (!(SPSR & (1 << SPIF))); - - SPDR = 0XFF; - } - - // transfer data - n = count - 1; - - for (uint16_t i = 0; i < n; i++) - { - while (!(SPSR & (1 << SPIF))); - - dst[i] = SPDR; - SPDR = 0XFF; - } - - // wait for last byte - while (!(SPSR & (1 << SPIF))); - - dst[n] = SPDR; - -#else // OPTIMIZE_HARDWARE_SPI - - // skip data before offset - for (; offset_ < offset; offset_++) - { - spiRec(); - } - - // transfer data - for (uint16_t i = 0; i < count; i++) - { - dst[i] = spiRec(); - } -#endif // OPTIMIZE_HARDWARE_SPI - - offset_ += count; - - if (!partialBlockRead_ || offset_ >= 512) - { - // read rest of data, checksum and set chip select high - readEnd(); - } - - return true; - -fail: - chipSelectHigh(); - return false; -} -//------------------------------------------------------------------------------ -/** Skip remaining data in a block when in partial block read mode. */ -void Sd2Card::readEnd() -{ - if (inBlock_) - { - // skip data and crc -#ifdef OPTIMIZE_HARDWARE_SPI - // optimize skip for hardware - SPDR = 0XFF; - - while (offset_++ < 513) - { - while (!(SPSR & (1 << SPIF))); - - SPDR = 0XFF; - } - - // wait for last crc byte - while (!(SPSR & (1 << SPIF))); - -#else // OPTIMIZE_HARDWARE_SPI - while (offset_++ < 514) - { - spiRec(); - } -#endif // OPTIMIZE_HARDWARE_SPI - - chipSelectHigh(); - inBlock_ = 0; - } -} -//------------------------------------------------------------------------------ -/** read CID or CSR register */ -uint8_t Sd2Card::readRegister(uint8_t cmd, void* buf) -{ - uint8_t* dst = reinterpret_cast(buf); - - if (cardCommand(cmd, 0)) - { - error(SD_CARD_ERROR_READ_REG); - goto fail; - } - - if (!waitStartBlock()) - { - goto fail; - } - - // transfer data - for (uint16_t i = 0; i < 16; i++) - { - dst[i] = spiRec(); - } - - spiRec(); // get first crc byte - spiRec(); // get second crc byte - chipSelectHigh(); - - return true; - -fail: - chipSelectHigh(); - - return false; -} -//------------------------------------------------------------------------------ -/** - Set the SPI clock rate. - - \param[in] sckRateID A value in the range [0, 6]. - - The SPI clock will be set to F_CPU/pow(2, 1 + sckRateID). The maximum - SPI rate is F_CPU/2 for \a sckRateID = 0 and the minimum rate is F_CPU/128 - for \a scsRateID = 6. - - \return The value one, true, is returned for success and the value zero, - false, is returned for an invalid value of \a sckRateID. -*/ -uint8_t Sd2Card::setSckRate(uint8_t sckRateID) -{ - if (sckRateID > 6) - { - error(SD_CARD_ERROR_SCK_RATE); - return false; - } - -#ifndef USE_SPI_LIB - - // see avr processor datasheet for SPI register bit definitions - if ((sckRateID & 1) || sckRateID == 6) - { - SPSR &= ~(1 << SPI2X); - } - else - { - SPSR |= (1 << SPI2X); - } - - SPCR &= ~((1 << SPR1) | (1 << SPR0)); - SPCR |= (sckRateID & 4 ? (1 << SPR1) : 0) | (sckRateID & 2 ? (1 << SPR0) : 0); - -#else // USE_SPI_LIB - - switch (sckRateID) - { - case 0: settings = SPISettings(25000000, MSBFIRST, SPI_MODE0); break; - case 1: settings = SPISettings(4000000, MSBFIRST, SPI_MODE0); break; - case 2: settings = SPISettings(2000000, MSBFIRST, SPI_MODE0); break; - case 3: settings = SPISettings(1000000, MSBFIRST, SPI_MODE0); break; - case 4: settings = SPISettings(500000, MSBFIRST, SPI_MODE0); break; - case 5: settings = SPISettings(250000, MSBFIRST, SPI_MODE0); break; - default: settings = SPISettings(125000, MSBFIRST, SPI_MODE0); - } - -#endif // USE_SPI_LIB - - return true; -} - -#ifdef USE_SPI_LIB -//------------------------------------------------------------------------------ -// set the SPI clock frequency -uint8_t Sd2Card::setSpiClock(uint32_t clock) -{ - settings = SPISettings(clock, MSBFIRST, SPI_MODE0); - - return true; -} -#endif - -//------------------------------------------------------------------------------ -// wait for card to go not busy -uint8_t Sd2Card::waitNotBusy(unsigned int timeoutMillis) -{ - unsigned int t0 = millis(); - unsigned int d; - - do - { - if (spiRec() == 0XFF) - { - return true; - } - - d = millis() - t0; - } while (d < timeoutMillis); - - return false; -} -//------------------------------------------------------------------------------ -/** Wait for start block token */ -uint8_t Sd2Card::waitStartBlock() -{ - unsigned int t0 = millis(); - - while ((status_ = spiRec()) == 0XFF) - { - unsigned int d = millis() - t0; - - if (d > SD_READ_TIMEOUT) - { - error(SD_CARD_ERROR_READ_TIMEOUT); - goto fail; - } - } - - if (status_ != DATA_START_BLOCK) - { - error(SD_CARD_ERROR_READ); - goto fail; - } - - return true; - -fail: - chipSelectHigh(); - - return false; -} -//------------------------------------------------------------------------------ -/** - Writes a 512 byte block to an SD card. - - \param[in] blockNumber Logical block to be written. - \param[in] src Pointer to the location of the data to be written. - \param[in] blocking If the write should be blocking. - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. -*/ -uint8_t Sd2Card::writeBlock(uint32_t blockNumber, const uint8_t* src, uint8_t blocking) -{ -#if SD_PROTECT_BLOCK_ZERO - // don't allow write to first block - if (blockNumber == 0) - { - error(SD_CARD_ERROR_WRITE_BLOCK_ZERO); - goto fail; - } -#endif // SD_PROTECT_BLOCK_ZERO - - // use address if not SDHC card - if (type() != SD_CARD_TYPE_SDHC) - { - blockNumber <<= 9; - } - - if (cardCommand(CMD24, blockNumber)) - { - error(SD_CARD_ERROR_CMD24); - goto fail; - } - - if (!writeData(DATA_START_BLOCK, src)) - { - goto fail; - } - - if (blocking) - { - // wait for flash programming to complete - if (!waitNotBusy(SD_WRITE_TIMEOUT)) - { - error(SD_CARD_ERROR_WRITE_TIMEOUT); - goto fail; - } - - // response is r2 so get and check two bytes for nonzero - if (cardCommand(CMD13, 0) || spiRec()) - { - error(SD_CARD_ERROR_WRITE_PROGRAMMING); - goto fail; - } - } - - chipSelectHigh(); - return true; - -fail: - chipSelectHigh(); - - return false; -} -//------------------------------------------------------------------------------ -/** Write one data block in a multiple block write sequence */ -uint8_t Sd2Card::writeData(const uint8_t* src) -{ - // wait for previous write to finish - if (!waitNotBusy(SD_WRITE_TIMEOUT)) - { - error(SD_CARD_ERROR_WRITE_MULTIPLE); - chipSelectHigh(); - - return false; - } - - return writeData(WRITE_MULTIPLE_TOKEN, src); -} -//------------------------------------------------------------------------------ -// send one block of data for write block or write multiple blocks -uint8_t Sd2Card::writeData(uint8_t token, const uint8_t* src) -{ -#ifdef OPTIMIZE_HARDWARE_SPI - - // send data - optimized loop - SPDR = token; - - // send two byte per iteration - for (uint16_t i = 0; i < 512; i += 2) - { - while (!(SPSR & (1 << SPIF))); - - SPDR = src[i]; - - while (!(SPSR & (1 << SPIF))); - - SPDR = src[i + 1]; - } - - // wait for last data byte - while (!(SPSR & (1 << SPIF))); - -#else // OPTIMIZE_HARDWARE_SPI - - spiSend(token); - - for (uint16_t i = 0; i < 512; i++) - { - spiSend(src[i]); - } - -#endif // OPTIMIZE_HARDWARE_SPI - - spiSend(0xff); // dummy crc - spiSend(0xff); // dummy crc - - status_ = spiRec(); - - if ((status_ & DATA_RES_MASK) != DATA_RES_ACCEPTED) - { - error(SD_CARD_ERROR_WRITE); - chipSelectHigh(); - return false; - } - - return true; -} -//------------------------------------------------------------------------------ -/** Start a write multiple blocks sequence. - - \param[in] blockNumber Address of first block in sequence. - \param[in] eraseCount The number of blocks to be pre-erased. - - \note This function is used with writeData() and writeStop() - for optimized multiple block writes. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. -*/ -uint8_t Sd2Card::writeStart(uint32_t blockNumber, uint32_t eraseCount) -{ -#if SD_PROTECT_BLOCK_ZERO - // don't allow write to first block - if (blockNumber == 0) - { - error(SD_CARD_ERROR_WRITE_BLOCK_ZERO); - goto fail; - } -#endif // SD_PROTECT_BLOCK_ZERO - - // send pre-erase count - if (cardAcmd(ACMD23, eraseCount)) - { - error(SD_CARD_ERROR_ACMD23); - goto fail; - } - - // use address if not SDHC card - if (type() != SD_CARD_TYPE_SDHC) - { - blockNumber <<= 9; - } - - if (cardCommand(CMD25, blockNumber)) - { - error(SD_CARD_ERROR_CMD25); - goto fail; - } - - return true; - -fail: - chipSelectHigh(); - - return false; -} -//------------------------------------------------------------------------------ -/** End a write multiple blocks sequence. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. -*/ -uint8_t Sd2Card::writeStop() -{ - if (!waitNotBusy(SD_WRITE_TIMEOUT)) - { - goto fail; - } - - spiSend(STOP_TRAN_TOKEN); - - if (!waitNotBusy(SD_WRITE_TIMEOUT)) - { - goto fail; - } - - chipSelectHigh(); - return true; - -fail: - error(SD_CARD_ERROR_STOP_TRAN); - chipSelectHigh(); - - return false; -} -//------------------------------------------------------------------------------ -/** Check if the SD card is busy - - \return The value one, true, is returned when is busy and - the value zero, false, is returned for when is NOT busy. -*/ -uint8_t Sd2Card::isBusy() -{ - chipSelectLow(); - byte b = spiRec(); - chipSelectHigh(); - - return (b != 0XFF); -} +/**************************************************************************************************************************** + Sd2Card.cpp + + For all RP2040 boads using Arduimo-mbed or arduino-pico core + + RP2040_SD is a library enable the usage of SD on RP2040-based boards + + This Library 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 Library 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 the Arduino SdFat Library. + If not, see . + + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) + + (C) Copyright 2009 by William Greiman + (C) Copyright 2010 SparkFun Electronics + (C) Copyright 2021 by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD + Licensed under GPL-3.0 license + + Version: 1.0.1 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core + 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO + *****************************************************************************************************************************/ + +#define USE_SPI_LIB +#include +#include "Sd2Card.h" + +//------------------------------------------------------------------------------ + +#if ! (RP2040_SOFT_SPI) + +#ifdef USE_SPI_LIB + + #ifndef SDCARD_SPI + #define SDCARD_SPI SPI + #endif + + #include + static SPISettings settings; + +#endif + +// functions for hardware SPI +/** Send a byte to the card */ +static void spiSend(uint8_t b) +{ +#ifndef USE_SPI_LIB + SPDR = b; + + while (!(SPSR & (1 << SPIF))); + +#else + SDCARD_SPI.transfer(b); +#endif +} + +/** Receive a byte from the card */ +static uint8_t spiRec() +{ +#ifndef USE_SPI_LIB + spiSend(0XFF); + return SPDR; +#else + return SDCARD_SPI.transfer(0xFF); +#endif +} + +#else // SOFTWARE_SPI + +//------------------------------------------------------------------------------ +/** nop to tune soft SPI timing */ +#define nop asm volatile ("nop\n\t") +//------------------------------------------------------------------------------ +/** Soft SPI receive */ +uint8_t spiRec() +{ + uint8_t data = 0; + // no interrupts during byte receive - about 8 us + cli(); + // output pin high - like sending 0XFF + fastDigitalWrite(SPI_MOSI_PIN, HIGH); + + for (uint8_t i = 0; i < 8; i++) + { + fastDigitalWrite(SPI_SCK_PIN, HIGH); + + // adjust so SCK is nice + nop; + nop; + + data <<= 1; + + if (fastDigitalRead(SPI_MISO_PIN)) + { + data |= 1; + } + + fastDigitalWrite(SPI_SCK_PIN, LOW); + } + + // enable interrupts + sei(); + + return data; +} + +//------------------------------------------------------------------------------ +/** Soft SPI send */ +void spiSend(uint8_t data) +{ + // no interrupts during byte send - about 8 us + cli(); + + for (uint8_t i = 0; i < 8; i++) + { + fastDigitalWrite(SPI_SCK_PIN, LOW); + + fastDigitalWrite(SPI_MOSI_PIN, data & 0X80); + + data <<= 1; + + fastDigitalWrite(SPI_SCK_PIN, HIGH); + } + + // hold SCK high for a few ns + nop; + nop; + nop; + nop; + + fastDigitalWrite(SPI_SCK_PIN, LOW); + // enable interrupts + sei(); +} + +#endif // SOFTWARE_SPI + +//------------------------------------------------------------------------------ +// send command and return error code. Return zero for OK +uint8_t Sd2Card::cardCommand(uint8_t cmd, uint32_t arg) +{ + // end read if in partialBlockRead mode + readEnd(); + + // select card + chipSelectLow(); + + // wait up to 300 ms if busy + waitNotBusy(300); + + // send command + spiSend(cmd | 0x40); + + // send argument + for (int8_t s = 24; s >= 0; s -= 8) + { + spiSend(arg >> s); + } + + // send CRC + uint8_t crc = 0XFF; + + if (cmd == CMD0) + { + crc = 0X95; // correct crc for CMD0 with arg 0 + } + + if (cmd == CMD8) + { + crc = 0X87; // correct crc for CMD8 with arg 0X1AA + } + + spiSend(crc); + + // wait for response + for (uint8_t i = 0; ((status_ = spiRec()) & 0X80) && i != 0XFF; i++); + + return status_; +} + +//------------------------------------------------------------------------------ +/** + Determine the size of an SD flash memory card. + + \return The number of 512 byte data blocks in the card + or zero if an error occurs. +*/ +uint32_t Sd2Card::cardSize() +{ + csd_t csd; + + if (!readCSD(&csd)) + { + return 0; + } + + if (csd.v1.csd_ver == 0) + { + uint8_t read_bl_len = csd.v1.read_bl_len; + uint16_t c_size = (csd.v1.c_size_high << 10) | (csd.v1.c_size_mid << 2) | csd.v1.c_size_low; + uint8_t c_size_mult = (csd.v1.c_size_mult_high << 1) | csd.v1.c_size_mult_low; + + return (uint32_t)(c_size + 1) << (c_size_mult + read_bl_len - 7); + } + else if (csd.v2.csd_ver == 1) + { + uint32_t c_size = ((uint32_t)csd.v2.c_size_high << 16) | (csd.v2.c_size_mid << 8) | csd.v2.c_size_low; + + return (c_size + 1) << 10; + } + else + { + error(SD_CARD_ERROR_BAD_CSD); + + return 0; + } +} +//------------------------------------------------------------------------------ +static uint8_t chip_select_asserted = 0; + +void Sd2Card::chipSelectHigh() +{ + digitalWrite(chipSelectPin_, HIGH); + +#ifdef USE_SPI_LIB + + if (chip_select_asserted) + { + chip_select_asserted = 0; + SDCARD_SPI.endTransaction(); + } + +#endif +} +//------------------------------------------------------------------------------ +void Sd2Card::chipSelectLow() +{ +#ifdef USE_SPI_LIB + + if (!chip_select_asserted) + { + chip_select_asserted = 1; + SDCARD_SPI.beginTransaction(settings); + } + +#endif + + digitalWrite(chipSelectPin_, LOW); +} +//------------------------------------------------------------------------------ +/** Erase a range of blocks. + + \param[in] firstBlock The address of the first block in the range. + \param[in] lastBlock The address of the last block in the range. + + \note This function requests the SD card to do a flash erase for a + range of blocks. The data on the card after an erase operation is + either 0 or 1, depends on the card vendor. The card must support + single block erase. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. +*/ +uint8_t Sd2Card::erase(uint32_t firstBlock, uint32_t lastBlock) +{ + if (!eraseSingleBlockEnable()) + { + error(SD_CARD_ERROR_ERASE_SINGLE_BLOCK); + goto fail; + } + + if (type_ != SD_CARD_TYPE_SDHC) + { + firstBlock <<= 9; + lastBlock <<= 9; + } + + if (cardCommand(CMD32, firstBlock) || cardCommand(CMD33, lastBlock) || cardCommand(CMD38, 0)) + { + error(SD_CARD_ERROR_ERASE); + goto fail; + } + + if (!waitNotBusy(SD_ERASE_TIMEOUT)) + { + error(SD_CARD_ERROR_ERASE_TIMEOUT); + goto fail; + } + + chipSelectHigh(); + + return true; + +fail: + chipSelectHigh(); + + return false; +} +//------------------------------------------------------------------------------ +/** Determine if card supports single block erase. + + \return The value one, true, is returned if single block erase is supported. + The value zero, false, is returned if single block erase is not supported. +*/ +uint8_t Sd2Card::eraseSingleBlockEnable() +{ + csd_t csd; + return readCSD(&csd) ? csd.v1.erase_blk_en : 0; +} +//------------------------------------------------------------------------------ +/** + Initialize an SD flash memory card. + + \param[in] sckRateID SPI clock rate selector. See setSckRate(). + \param[in] chipSelectPin SD chip select pin number. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. The reason for failure + can be determined by calling errorCode() and errorData(). +*/ +uint8_t Sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin) +{ + errorCode_ = inBlock_ = partialBlockRead_ = type_ = 0; + chipSelectPin_ = chipSelectPin; + + // 16-bit init start time allows over a minute + unsigned int t0 = millis(); + uint32_t arg; + + // set pin modes + pinMode(chipSelectPin_, OUTPUT); + digitalWrite(chipSelectPin_, HIGH); + +#ifndef USE_SPI_LIB + pinMode(SPI_MISO_PIN, INPUT); + pinMode(SPI_MOSI_PIN, OUTPUT); + pinMode(SPI_SCK_PIN, OUTPUT); +#endif + +#ifndef SOFTWARE_SPI +#ifndef USE_SPI_LIB + // SS must be in output mode even it is not chip select + pinMode(SS_PIN, OUTPUT); + digitalWrite(SS_PIN, HIGH); // disable any SPI device using hardware SS pin + // Enable SPI, Master, clock rate f_osc/128 + SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0); + // clear double speed + SPSR &= ~(1 << SPI2X); +#else // USE_SPI_LIB + SDCARD_SPI.begin(); + settings = SPISettings(250000, MSBFIRST, SPI_MODE0); +#endif // USE_SPI_LIB +#endif // SOFTWARE_SPI + + // must supply min of 74 clock cycles with CS high. +#ifdef USE_SPI_LIB + SDCARD_SPI.beginTransaction(settings); +#endif + + for (uint8_t i = 0; i < 10; i++) + { + spiSend(0XFF); + } + +#ifdef USE_SPI_LIB + SDCARD_SPI.endTransaction(); +#endif + + chipSelectLow(); + + // command to go idle in SPI mode + while ((status_ = cardCommand(CMD0, 0)) != R1_IDLE_STATE) + { + unsigned int d = millis() - t0; + + if (d > SD_INIT_TIMEOUT) + { + error(SD_CARD_ERROR_CMD0); + goto fail; + } + } + + // check SD version + if ((cardCommand(CMD8, 0x1AA) & R1_ILLEGAL_COMMAND)) + { + type(SD_CARD_TYPE_SD1); + } + else + { + // only need last byte of r7 response + for (uint8_t i = 0; i < 4; i++) + { + status_ = spiRec(); + } + + if (status_ != 0XAA) + { + error(SD_CARD_ERROR_CMD8); + goto fail; + } + + type(SD_CARD_TYPE_SD2); + } + + // initialize card and send host supports SDHC if SD2 + arg = type() == SD_CARD_TYPE_SD2 ? 0X40000000 : 0; + + while ((status_ = cardAcmd(ACMD41, arg)) != R1_READY_STATE) + { + // check for timeout + unsigned int d = millis() - t0; + + if (d > SD_INIT_TIMEOUT) + { + error(SD_CARD_ERROR_ACMD41); + goto fail; + } + } + + // if SD2 read OCR register to check for SDHC card + if (type() == SD_CARD_TYPE_SD2) + { + if (cardCommand(CMD58, 0)) + { + error(SD_CARD_ERROR_CMD58); + goto fail; + } + + if ((spiRec() & 0XC0) == 0XC0) + { + type(SD_CARD_TYPE_SDHC); + } + + // discard rest of ocr - contains allowed voltage range + for (uint8_t i = 0; i < 3; i++) + { + spiRec(); + } + } + + chipSelectHigh(); + +#ifndef SOFTWARE_SPI + return setSckRate(sckRateID); +#else // SOFTWARE_SPI + return true; +#endif // SOFTWARE_SPI + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +/** + Enable or disable partial block reads. + + Enabling partial block reads improves performance by allowing a block + to be read over the SPI bus as several sub-blocks. Errors may occur + if the time between reads is too long since the SD card may timeout. + The SPI SS line will be held low until the entire block is read or + readEnd() is called. + + Use this for applications like the Adafruit Wave Shield. + + \param[in] value The value TRUE (non-zero) or FALSE (zero).) +*/ +void Sd2Card::partialBlockRead(uint8_t value) +{ + readEnd(); + partialBlockRead_ = value; +} +//------------------------------------------------------------------------------ +/** + Read a 512 byte block from an SD card device. + + \param[in] block Logical block to be read. + \param[out] dst Pointer to the location that will receive the data. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. +*/ +uint8_t Sd2Card::readBlock(uint32_t block, uint8_t* dst) +{ + return readData(block, 0, 512, dst); +} +//------------------------------------------------------------------------------ +/** + Read part of a 512 byte block from an SD card. + + \param[in] block Logical block to be read. + \param[in] offset Number of bytes to skip at start of block + \param[out] dst Pointer to the location that will receive the data. + \param[in] count Number of bytes to read + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. +*/ +uint8_t Sd2Card::readData(uint32_t block, uint16_t offset, uint16_t count, uint8_t* dst) +{ + if (count == 0) + { + return true; + } + + if ((count + offset) > 512) + { + goto fail; + } + + if (!inBlock_ || block != block_ || offset < offset_) + { + block_ = block; + + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) + { + block <<= 9; + } + + if (cardCommand(CMD17, block)) + { + error(SD_CARD_ERROR_CMD17); + goto fail; + } + + if (!waitStartBlock()) + { + goto fail; + } + + offset_ = 0; + inBlock_ = 1; + } + +#ifdef OPTIMIZE_HARDWARE_SPI + // start first spi transfer + SPDR = 0XFF; + + // skip data before offset + for (; offset_ < offset; offset_++) + { + while (!(SPSR & (1 << SPIF))); + + SPDR = 0XFF; + } + + // transfer data + n = count - 1; + + for (uint16_t i = 0; i < n; i++) + { + while (!(SPSR & (1 << SPIF))); + + dst[i] = SPDR; + SPDR = 0XFF; + } + + // wait for last byte + while (!(SPSR & (1 << SPIF))); + + dst[n] = SPDR; + +#else // OPTIMIZE_HARDWARE_SPI + + // skip data before offset + for (; offset_ < offset; offset_++) + { + spiRec(); + } + + // transfer data + for (uint16_t i = 0; i < count; i++) + { + dst[i] = spiRec(); + } + +#endif // OPTIMIZE_HARDWARE_SPI + + offset_ += count; + + if (!partialBlockRead_ || offset_ >= 512) + { + // read rest of data, checksum and set chip select high + readEnd(); + } + + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +/** Skip remaining data in a block when in partial block read mode. */ +void Sd2Card::readEnd() +{ + if (inBlock_) + { + // skip data and crc +#ifdef OPTIMIZE_HARDWARE_SPI + // optimize skip for hardware + SPDR = 0XFF; + + while (offset_++ < 513) + { + while (!(SPSR & (1 << SPIF))); + + SPDR = 0XFF; + } + + // wait for last crc byte + while (!(SPSR & (1 << SPIF))); + +#else // OPTIMIZE_HARDWARE_SPI + + while (offset_++ < 514) + { + spiRec(); + } + +#endif // OPTIMIZE_HARDWARE_SPI + + chipSelectHigh(); + inBlock_ = 0; + } +} +//------------------------------------------------------------------------------ +/** read CID or CSR register */ +uint8_t Sd2Card::readRegister(uint8_t cmd, void* buf) +{ + uint8_t* dst = reinterpret_cast(buf); + + if (cardCommand(cmd, 0)) + { + error(SD_CARD_ERROR_READ_REG); + goto fail; + } + + if (!waitStartBlock()) + { + goto fail; + } + + // transfer data + for (uint16_t i = 0; i < 16; i++) + { + dst[i] = spiRec(); + } + + spiRec(); // get first crc byte + spiRec(); // get second crc byte + chipSelectHigh(); + + return true; + +fail: + chipSelectHigh(); + + return false; +} +//------------------------------------------------------------------------------ +/** + Set the SPI clock rate. + + \param[in] sckRateID A value in the range [0, 6]. + + The SPI clock will be set to F_CPU/pow(2, 1 + sckRateID). The maximum + SPI rate is F_CPU/2 for \a sckRateID = 0 and the minimum rate is F_CPU/128 + for \a scsRateID = 6. + + \return The value one, true, is returned for success and the value zero, + false, is returned for an invalid value of \a sckRateID. +*/ +uint8_t Sd2Card::setSckRate(uint8_t sckRateID) +{ + if (sckRateID > 6) + { + error(SD_CARD_ERROR_SCK_RATE); + return false; + } + +#ifndef USE_SPI_LIB + + // see avr processor datasheet for SPI register bit definitions + if ((sckRateID & 1) || sckRateID == 6) + { + SPSR &= ~(1 << SPI2X); + } + else + { + SPSR |= (1 << SPI2X); + } + + SPCR &= ~((1 << SPR1) | (1 << SPR0)); + SPCR |= (sckRateID & 4 ? (1 << SPR1) : 0) | (sckRateID & 2 ? (1 << SPR0) : 0); + +#else // USE_SPI_LIB + + switch (sckRateID) + { + case 0: + settings = SPISettings(25000000, MSBFIRST, SPI_MODE0); + break; + + case 1: + settings = SPISettings(4000000, MSBFIRST, SPI_MODE0); + break; + + case 2: + settings = SPISettings(2000000, MSBFIRST, SPI_MODE0); + break; + + case 3: + settings = SPISettings(1000000, MSBFIRST, SPI_MODE0); + break; + + case 4: + settings = SPISettings(500000, MSBFIRST, SPI_MODE0); + break; + + case 5: + settings = SPISettings(250000, MSBFIRST, SPI_MODE0); + break; + + default: + settings = SPISettings(125000, MSBFIRST, SPI_MODE0); + } + +#endif // USE_SPI_LIB + + return true; +} + +#ifdef USE_SPI_LIB +//------------------------------------------------------------------------------ +// set the SPI clock frequency +uint8_t Sd2Card::setSpiClock(uint32_t clock) +{ + settings = SPISettings(clock, MSBFIRST, SPI_MODE0); + + return true; +} +#endif + +//------------------------------------------------------------------------------ +// wait for card to go not busy +uint8_t Sd2Card::waitNotBusy(unsigned int timeoutMillis) +{ + unsigned int t0 = millis(); + unsigned int d; + + do + { + if (spiRec() == 0XFF) + { + return true; + } + + d = millis() - t0; + } while (d < timeoutMillis); + + return false; +} +//------------------------------------------------------------------------------ +/** Wait for start block token */ +uint8_t Sd2Card::waitStartBlock() +{ + unsigned int t0 = millis(); + + while ((status_ = spiRec()) == 0XFF) + { + unsigned int d = millis() - t0; + + if (d > SD_READ_TIMEOUT) + { + error(SD_CARD_ERROR_READ_TIMEOUT); + goto fail; + } + } + + if (status_ != DATA_START_BLOCK) + { + error(SD_CARD_ERROR_READ); + goto fail; + } + + return true; + +fail: + chipSelectHigh(); + + return false; +} +//------------------------------------------------------------------------------ +/** + Writes a 512 byte block to an SD card. + + \param[in] blockNumber Logical block to be written. + \param[in] src Pointer to the location of the data to be written. + \param[in] blocking If the write should be blocking. + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. +*/ +uint8_t Sd2Card::writeBlock(uint32_t blockNumber, const uint8_t* src, uint8_t blocking) +{ +#if SD_PROTECT_BLOCK_ZERO + + // don't allow write to first block + if (blockNumber == 0) + { + error(SD_CARD_ERROR_WRITE_BLOCK_ZERO); + goto fail; + } + +#endif // SD_PROTECT_BLOCK_ZERO + + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) + { + blockNumber <<= 9; + } + + if (cardCommand(CMD24, blockNumber)) + { + error(SD_CARD_ERROR_CMD24); + goto fail; + } + + if (!writeData(DATA_START_BLOCK, src)) + { + goto fail; + } + + if (blocking) + { + // wait for flash programming to complete + if (!waitNotBusy(SD_WRITE_TIMEOUT)) + { + error(SD_CARD_ERROR_WRITE_TIMEOUT); + goto fail; + } + + // response is r2 so get and check two bytes for nonzero + if (cardCommand(CMD13, 0) || spiRec()) + { + error(SD_CARD_ERROR_WRITE_PROGRAMMING); + goto fail; + } + } + + chipSelectHigh(); + return true; + +fail: + chipSelectHigh(); + + return false; +} +//------------------------------------------------------------------------------ +/** Write one data block in a multiple block write sequence */ +uint8_t Sd2Card::writeData(const uint8_t* src) +{ + // wait for previous write to finish + if (!waitNotBusy(SD_WRITE_TIMEOUT)) + { + error(SD_CARD_ERROR_WRITE_MULTIPLE); + chipSelectHigh(); + + return false; + } + + return writeData(WRITE_MULTIPLE_TOKEN, src); +} +//------------------------------------------------------------------------------ +// send one block of data for write block or write multiple blocks +uint8_t Sd2Card::writeData(uint8_t token, const uint8_t* src) +{ +#ifdef OPTIMIZE_HARDWARE_SPI + + // send data - optimized loop + SPDR = token; + + // send two byte per iteration + for (uint16_t i = 0; i < 512; i += 2) + { + while (!(SPSR & (1 << SPIF))); + + SPDR = src[i]; + + while (!(SPSR & (1 << SPIF))); + + SPDR = src[i + 1]; + } + + // wait for last data byte + while (!(SPSR & (1 << SPIF))); + +#else // OPTIMIZE_HARDWARE_SPI + + spiSend(token); + + for (uint16_t i = 0; i < 512; i++) + { + spiSend(src[i]); + } + +#endif // OPTIMIZE_HARDWARE_SPI + + spiSend(0xff); // dummy crc + spiSend(0xff); // dummy crc + + status_ = spiRec(); + + if ((status_ & DATA_RES_MASK) != DATA_RES_ACCEPTED) + { + error(SD_CARD_ERROR_WRITE); + chipSelectHigh(); + return false; + } + + return true; +} +//------------------------------------------------------------------------------ +/** Start a write multiple blocks sequence. + + \param[in] blockNumber Address of first block in sequence. + \param[in] eraseCount The number of blocks to be pre-erased. + + \note This function is used with writeData() and writeStop() + for optimized multiple block writes. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. +*/ +uint8_t Sd2Card::writeStart(uint32_t blockNumber, uint32_t eraseCount) +{ +#if SD_PROTECT_BLOCK_ZERO + + // don't allow write to first block + if (blockNumber == 0) + { + error(SD_CARD_ERROR_WRITE_BLOCK_ZERO); + goto fail; + } + +#endif // SD_PROTECT_BLOCK_ZERO + + // send pre-erase count + if (cardAcmd(ACMD23, eraseCount)) + { + error(SD_CARD_ERROR_ACMD23); + goto fail; + } + + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) + { + blockNumber <<= 9; + } + + if (cardCommand(CMD25, blockNumber)) + { + error(SD_CARD_ERROR_CMD25); + goto fail; + } + + return true; + +fail: + chipSelectHigh(); + + return false; +} +//------------------------------------------------------------------------------ +/** End a write multiple blocks sequence. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. +*/ +uint8_t Sd2Card::writeStop() +{ + if (!waitNotBusy(SD_WRITE_TIMEOUT)) + { + goto fail; + } + + spiSend(STOP_TRAN_TOKEN); + + if (!waitNotBusy(SD_WRITE_TIMEOUT)) + { + goto fail; + } + + chipSelectHigh(); + return true; + +fail: + error(SD_CARD_ERROR_STOP_TRAN); + chipSelectHigh(); + + return false; +} +//------------------------------------------------------------------------------ +/** Check if the SD card is busy + + \return The value one, true, is returned when is busy and + the value zero, false, is returned for when is NOT busy. +*/ +uint8_t Sd2Card::isBusy() +{ + chipSelectLow(); + byte b = spiRec(); + chipSelectHigh(); + + return (b != 0XFF); +} diff --git a/src/utility/Sd2Card.h b/src/utility/Sd2Card.h index 4416e76..baf0339 100644 --- a/src/utility/Sd2Card.h +++ b/src/utility/Sd2Card.h @@ -1,306 +1,306 @@ -/**************************************************************************************************************************** - Sd2Card.h - - For all RP2040 boads using Arduimo-mbed or arduino-pico core - - RP2040_SD is a library enable the usage of SD on RP2040-based boards - - This Library 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 Library 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 the Arduino SdFat Library. - If not, see . - - Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - - (C) Copyright 2009 by William Greiman - (C) Copyright 2010 SparkFun Electronics - (C) Copyright 2021 by Khoi Hoang - - Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD - Licensed under GPL-3.0 license - - Version: 1.0.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core - 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO - *****************************************************************************************************************************/ - -#pragma once - -#ifndef Sd2Card_h -#define Sd2Card_h - -/** - \file - Sd2Card class -*/ -#include "Sd2PinMap.h" -#include "SdInfo.h" - -#include "RP2040_SD_Debug.h" - -/** Set SCK to max rate of F_CPU/2. See Sd2Card::setSckRate(). */ -#define SPI_FULL_SPEED 0 -/** Set SCK rate to F_CPU/4. See Sd2Card::setSckRate(). */ -#define SPI_HALF_SPEED 1 -/** Set SCK rate to F_CPU/8. Sd2Card::setSckRate(). */ -#define SPI_QUARTER_SPEED 2 - -/** - USE_SPI_LIB: if set, use the SPI library bundled with Arduino IDE, otherwise - run with a standalone driver for AVR. -*/ -#define USE_SPI_LIB - -/** - Define RP2040_SOFT_SPI true to use software SPI on RP2040 - Pins used are SS 10, MOSI 11, MISO 12, and SCK 13. - - RP2040_SOFT_SPI allows Software SPI works with RP2040 -*/ -#define RP2040_SOFT_SPI false - -//------------------------------------------------------------------------------ -// SPI pin definitions -// -#if ! (RP2040_SOFT_SPI) - // include pins_arduino.h or variant.h depending on architecture, via Arduino.h - #include - - #ifndef SDCARD_SS_PIN - /** The default chip select pin for the SD card is SS. */ - #define SD_CHIP_SELECT_PIN SS - #else - #define SD_CHIP_SELECT_PIN SDCARD_SS_PIN - #endif - - // The following three pins must not be redefined for hardware SPI, - // so ensure that they are taken from pins_arduino.h or variant.h, depending on architecture. - #ifndef SDCARD_MOSI_PIN - /** SPI Master Out Slave In pin */ - #define SPI_MOSI_PIN MOSI - /** SPI Master In Slave Out pin */ - #define SPI_MISO_PIN MISO - /** SPI Clock pin */ - #define SPI_SCK_PIN SCK - #else - #define SPI_MOSI_PIN SDCARD_MOSI_PIN - #define SPI_MISO_PIN SDCARD_MISO_PIN - #define SPI_SCK_PIN SDCARD_SCK_PIN - #endif - - /** optimize loops for hardware SPI */ - #ifndef USE_SPI_LIB - #define OPTIMIZE_HARDWARE_SPI - #endif - -#else - - // SOFTWARE_SPI - // define software SPI pins so RP2040 can use SW SPI - /** SPI chip select pin */ - #define SD_CHIP_SELECT_PIN 10 - /** SPI Master Out Slave In pin */ - #define SPI_MOSI_PIN 11 - /** SPI Master In Slave Out pin */ - #define SPI_MISO_PIN 12 - /** SPI Clock pin */ - #define SPI_SCK_PIN 13 -#endif // SOFTWARE_SPI - - -//------------------------------------------------------------------------------ -/** Protect block zero from write if nonzero */ -#define SD_PROTECT_BLOCK_ZERO 1 - -/** init timeout ms */ -#define SD_INIT_TIMEOUT 2000 - -/** erase timeout ms */ -#define SD_ERASE_TIMEOUT 10000 - -/** read timeout ms */ -#define SD_READ_TIMEOUT 300 - -/** write time out ms */ -#define SD_WRITE_TIMEOUT 600 - -//------------------------------------------------------------------------------ -// SD card errors - -enum -{ - SD_CARD_ERROR_CMD0 = 0x01, - SD_CARD_ERROR_CMD8 = 0x02, - SD_CARD_ERROR_CMD17 = 0x03, - SD_CARD_ERROR_CMD24 = 0x04, - SD_CARD_ERROR_CMD25 = 0x05, - SD_CARD_ERROR_CMD58 = 0x06, - SD_CARD_ERROR_ACMD23 = 0x07, - SD_CARD_ERROR_ACMD41 = 0x08, - SD_CARD_ERROR_BAD_CSD = 0x09, - SD_CARD_ERROR_ERASE = 0x0A, - SD_CARD_ERROR_ERASE_SINGLE_BLOCK = 0x0B, - SD_CARD_ERROR_ERASE_TIMEOUT = 0x0C, - SD_CARD_ERROR_READ = 0x0D, - SD_CARD_ERROR_READ_REG = 0x0E, - SD_CARD_ERROR_READ_TIMEOUT = 0x0F, - SD_CARD_ERROR_STOP_TRAN = 0x10, - SD_CARD_ERROR_WRITE = 0x11, - SD_CARD_ERROR_WRITE_BLOCK_ZERO = 0x12, - SD_CARD_ERROR_WRITE_MULTIPLE = 0x13, - SD_CARD_ERROR_WRITE_PROGRAMMING = 0x14, - SD_CARD_ERROR_WRITE_TIMEOUT = 0x15, - SD_CARD_ERROR_SCK_RATE = 0X16, -}; - - -//------------------------------------------------------------------------------ -// card types -enum -{ - SD_CARD_TYPE_SD1 = 1, - SD_CARD_TYPE_SD2 = 2, - SD_CARD_TYPE_SDHC = 3, -}; - -//------------------------------------------------------------------------------ - -class Sd2Card -{ - public: - - Sd2Card() : errorCode_(0), inBlock_(0), partialBlockRead_(0), type_(0) {} - - uint32_t cardSize(); - uint8_t erase(uint32_t firstBlock, uint32_t lastBlock); - uint8_t eraseSingleBlockEnable(); - - /** - \return error code for last error. See Sd2Card.h for a list of error codes. - */ - uint8_t errorCode() const - { - return errorCode_; - } - - /** \return error data for last error. */ - uint8_t errorData() const - { - return status_; - } - - /** - Initialize an SD flash memory card with default clock rate and chip - select pin. See sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin). - */ - uint8_t init() - { - return init(SPI_FULL_SPEED, SD_CHIP_SELECT_PIN); - } - - /** - Initialize an SD flash memory card with the selected SPI clock rate - and the default SD chip select pin. - See sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin). - */ - uint8_t init(uint8_t sckRateID) - { - return init(sckRateID, SD_CHIP_SELECT_PIN); - } - - uint8_t init(uint8_t sckRateID, uint8_t chipSelectPin); - - void partialBlockRead(uint8_t value); - - /** Returns the current value, true or false, for partial block read. */ - uint8_t partialBlockRead() const - { - return partialBlockRead_; - } - - uint8_t readBlock(uint32_t block, uint8_t* dst); - uint8_t readData(uint32_t block, uint16_t offset, uint16_t count, uint8_t* dst); - - /** - Read a cards CID register. The CID contains card identification - information such as Manufacturer ID, Product name, Product serial - number and Manufacturing date. */ - uint8_t readCID(cid_t* cid) - { - return readRegister(CMD10, cid); - } - - /** - Read a cards CSD register. The CSD contains Card-Specific Data that - provides information regarding access to the card's contents. */ - uint8_t readCSD(csd_t* csd) - { - return readRegister(CMD9, csd); - } - - void readEnd(); - uint8_t setSckRate(uint8_t sckRateID); - -#ifdef USE_SPI_LIB - uint8_t setSpiClock(uint32_t clock); -#endif - - /** Return the card type: SD V1, SD V2 or SDHC */ - uint8_t type() const - { - return type_; - } - - uint8_t writeBlock(uint32_t blockNumber, const uint8_t* src, uint8_t blocking = 1); - uint8_t writeData(const uint8_t* src); - uint8_t writeStart(uint32_t blockNumber, uint32_t eraseCount); - uint8_t writeStop(); - uint8_t isBusy(); - - private: - - uint32_t block_; - uint8_t chipSelectPin_; - uint8_t errorCode_; - uint8_t inBlock_; - uint16_t offset_; - uint8_t partialBlockRead_; - uint8_t status_; - uint8_t type_; - - // private functions - uint8_t cardAcmd(uint8_t cmd, uint32_t arg) - { - cardCommand(CMD55, 0); - return cardCommand(cmd, arg); - } - - uint8_t cardCommand(uint8_t cmd, uint32_t arg); - - void error(uint8_t code) - { - errorCode_ = code; - } - - uint8_t readRegister(uint8_t cmd, void* buf); - uint8_t sendWriteCommand(uint32_t blockNumber, uint32_t eraseCount); - void chipSelectHigh(); - void chipSelectLow(); - - void type(uint8_t value) - { - type_ = value; - } - - uint8_t waitNotBusy(unsigned int timeoutMillis); - uint8_t writeData(uint8_t token, const uint8_t* src); - uint8_t waitStartBlock(); -}; -#endif // Sd2Card_h +/**************************************************************************************************************************** + Sd2Card.h + + For all RP2040 boads using Arduimo-mbed or arduino-pico core + + RP2040_SD is a library enable the usage of SD on RP2040-based boards + + This Library 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 Library 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 the Arduino SdFat Library. + If not, see . + + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) + + (C) Copyright 2009 by William Greiman + (C) Copyright 2010 SparkFun Electronics + (C) Copyright 2021 by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD + Licensed under GPL-3.0 license + + Version: 1.0.1 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core + 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO + *****************************************************************************************************************************/ + +#pragma once + +#ifndef Sd2Card_h +#define Sd2Card_h + +/** + \file + Sd2Card class +*/ +#include "Sd2PinMap.h" +#include "SdInfo.h" + +#include "RP2040_SD_Debug.h" + +/** Set SCK to max rate of F_CPU/2. See Sd2Card::setSckRate(). */ +#define SPI_FULL_SPEED 0 +/** Set SCK rate to F_CPU/4. See Sd2Card::setSckRate(). */ +#define SPI_HALF_SPEED 1 +/** Set SCK rate to F_CPU/8. Sd2Card::setSckRate(). */ +#define SPI_QUARTER_SPEED 2 + +/** + USE_SPI_LIB: if set, use the SPI library bundled with Arduino IDE, otherwise + run with a standalone driver for AVR. +*/ +#define USE_SPI_LIB + +/** + Define RP2040_SOFT_SPI true to use software SPI on RP2040 + Pins used are SS 10, MOSI 11, MISO 12, and SCK 13. + + RP2040_SOFT_SPI allows Software SPI works with RP2040 +*/ +#define RP2040_SOFT_SPI false + +//------------------------------------------------------------------------------ +// SPI pin definitions +// +#if ! (RP2040_SOFT_SPI) + // include pins_arduino.h or variant.h depending on architecture, via Arduino.h + #include + + #ifndef SDCARD_SS_PIN + /** The default chip select pin for the SD card is SS. */ + #define SD_CHIP_SELECT_PIN SS + #else + #define SD_CHIP_SELECT_PIN SDCARD_SS_PIN + #endif + + // The following three pins must not be redefined for hardware SPI, + // so ensure that they are taken from pins_arduino.h or variant.h, depending on architecture. + #ifndef SDCARD_MOSI_PIN + /** SPI Master Out Slave In pin */ + #define SPI_MOSI_PIN MOSI + /** SPI Master In Slave Out pin */ + #define SPI_MISO_PIN MISO + /** SPI Clock pin */ + #define SPI_SCK_PIN SCK + #else + #define SPI_MOSI_PIN SDCARD_MOSI_PIN + #define SPI_MISO_PIN SDCARD_MISO_PIN + #define SPI_SCK_PIN SDCARD_SCK_PIN + #endif + + /** optimize loops for hardware SPI */ + #ifndef USE_SPI_LIB + #define OPTIMIZE_HARDWARE_SPI + #endif + +#else + + // SOFTWARE_SPI + // define software SPI pins so RP2040 can use SW SPI + /** SPI chip select pin */ + #define SD_CHIP_SELECT_PIN 10 + /** SPI Master Out Slave In pin */ + #define SPI_MOSI_PIN 11 + /** SPI Master In Slave Out pin */ + #define SPI_MISO_PIN 12 + /** SPI Clock pin */ + #define SPI_SCK_PIN 13 +#endif // SOFTWARE_SPI + + +//------------------------------------------------------------------------------ +/** Protect block zero from write if nonzero */ +#define SD_PROTECT_BLOCK_ZERO 1 + +/** init timeout ms */ +#define SD_INIT_TIMEOUT 2000 + +/** erase timeout ms */ +#define SD_ERASE_TIMEOUT 10000 + +/** read timeout ms */ +#define SD_READ_TIMEOUT 300 + +/** write time out ms */ +#define SD_WRITE_TIMEOUT 600 + +//------------------------------------------------------------------------------ +// SD card errors + +enum +{ + SD_CARD_ERROR_CMD0 = 0x01, + SD_CARD_ERROR_CMD8 = 0x02, + SD_CARD_ERROR_CMD17 = 0x03, + SD_CARD_ERROR_CMD24 = 0x04, + SD_CARD_ERROR_CMD25 = 0x05, + SD_CARD_ERROR_CMD58 = 0x06, + SD_CARD_ERROR_ACMD23 = 0x07, + SD_CARD_ERROR_ACMD41 = 0x08, + SD_CARD_ERROR_BAD_CSD = 0x09, + SD_CARD_ERROR_ERASE = 0x0A, + SD_CARD_ERROR_ERASE_SINGLE_BLOCK = 0x0B, + SD_CARD_ERROR_ERASE_TIMEOUT = 0x0C, + SD_CARD_ERROR_READ = 0x0D, + SD_CARD_ERROR_READ_REG = 0x0E, + SD_CARD_ERROR_READ_TIMEOUT = 0x0F, + SD_CARD_ERROR_STOP_TRAN = 0x10, + SD_CARD_ERROR_WRITE = 0x11, + SD_CARD_ERROR_WRITE_BLOCK_ZERO = 0x12, + SD_CARD_ERROR_WRITE_MULTIPLE = 0x13, + SD_CARD_ERROR_WRITE_PROGRAMMING = 0x14, + SD_CARD_ERROR_WRITE_TIMEOUT = 0x15, + SD_CARD_ERROR_SCK_RATE = 0X16, +}; + + +//------------------------------------------------------------------------------ +// card types +enum +{ + SD_CARD_TYPE_SD1 = 1, + SD_CARD_TYPE_SD2 = 2, + SD_CARD_TYPE_SDHC = 3, +}; + +//------------------------------------------------------------------------------ + +class Sd2Card +{ + public: + + Sd2Card() : errorCode_(0), inBlock_(0), partialBlockRead_(0), type_(0) {} + + uint32_t cardSize(); + uint8_t erase(uint32_t firstBlock, uint32_t lastBlock); + uint8_t eraseSingleBlockEnable(); + + /** + \return error code for last error. See Sd2Card.h for a list of error codes. + */ + uint8_t errorCode() const + { + return errorCode_; + } + + /** \return error data for last error. */ + uint8_t errorData() const + { + return status_; + } + + /** + Initialize an SD flash memory card with default clock rate and chip + select pin. See sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin). + */ + uint8_t init() + { + return init(SPI_FULL_SPEED, SD_CHIP_SELECT_PIN); + } + + /** + Initialize an SD flash memory card with the selected SPI clock rate + and the default SD chip select pin. + See sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin). + */ + uint8_t init(uint8_t sckRateID) + { + return init(sckRateID, SD_CHIP_SELECT_PIN); + } + + uint8_t init(uint8_t sckRateID, uint8_t chipSelectPin); + + void partialBlockRead(uint8_t value); + + /** Returns the current value, true or false, for partial block read. */ + uint8_t partialBlockRead() const + { + return partialBlockRead_; + } + + uint8_t readBlock(uint32_t block, uint8_t* dst); + uint8_t readData(uint32_t block, uint16_t offset, uint16_t count, uint8_t* dst); + + /** + Read a cards CID register. The CID contains card identification + information such as Manufacturer ID, Product name, Product serial + number and Manufacturing date. */ + uint8_t readCID(cid_t* cid) + { + return readRegister(CMD10, cid); + } + + /** + Read a cards CSD register. The CSD contains Card-Specific Data that + provides information regarding access to the card's contents. */ + uint8_t readCSD(csd_t* csd) + { + return readRegister(CMD9, csd); + } + + void readEnd(); + uint8_t setSckRate(uint8_t sckRateID); + +#ifdef USE_SPI_LIB + uint8_t setSpiClock(uint32_t clock); +#endif + + /** Return the card type: SD V1, SD V2 or SDHC */ + uint8_t type() const + { + return type_; + } + + uint8_t writeBlock(uint32_t blockNumber, const uint8_t* src, uint8_t blocking = 1); + uint8_t writeData(const uint8_t* src); + uint8_t writeStart(uint32_t blockNumber, uint32_t eraseCount); + uint8_t writeStop(); + uint8_t isBusy(); + + private: + + uint32_t block_; + uint8_t chipSelectPin_; + uint8_t errorCode_; + uint8_t inBlock_; + uint16_t offset_; + uint8_t partialBlockRead_; + uint8_t status_; + uint8_t type_; + + // private functions + uint8_t cardAcmd(uint8_t cmd, uint32_t arg) + { + cardCommand(CMD55, 0); + return cardCommand(cmd, arg); + } + + uint8_t cardCommand(uint8_t cmd, uint32_t arg); + + void error(uint8_t code) + { + errorCode_ = code; + } + + uint8_t readRegister(uint8_t cmd, void* buf); + uint8_t sendWriteCommand(uint32_t blockNumber, uint32_t eraseCount); + void chipSelectHigh(); + void chipSelectLow(); + + void type(uint8_t value) + { + type_ = value; + } + + uint8_t waitNotBusy(unsigned int timeoutMillis); + uint8_t writeData(uint8_t token, const uint8_t* src); + uint8_t waitStartBlock(); +}; +#endif // Sd2Card_h diff --git a/src/utility/Sd2PinMap.h b/src/utility/Sd2PinMap.h index 18af124..0e31733 100644 --- a/src/utility/Sd2PinMap.h +++ b/src/utility/Sd2PinMap.h @@ -1,53 +1,53 @@ -/**************************************************************************************************************************** - Sd2PinMap.h - - For all RP2040 boads using Arduimo-mbed or arduino-pico core - - RP2040_SD is a library enable the usage of SD on RP2040-based boards - - This Library 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 Library 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 the Arduino SdFat Library. - If not, see . - - Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - - (C) Copyright 2009 by William Greiman - (C) Copyright 2010 SparkFun Electronics - (C) Copyright 2021 by Khoi Hoang - - Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD - Licensed under GPL-3.0 license - - Version: 1.0.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core - 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO - *****************************************************************************************************************************/ - -#pragma once - -#ifndef Sd2PinMap_h -#define Sd2PinMap_h - - #if defined(ARDUINO_ARCH_RP2040) - - #include - - #define SS_PIN SS - #define MOSI_PIN MOSI - #define MISO_PIN MISO - #define SCK_PIN SCK - - #else - #error For RP2040 Architecture only. - #endif - -#endif // Sd2PinMap_h - +/**************************************************************************************************************************** + Sd2PinMap.h + + For all RP2040 boads using Arduimo-mbed or arduino-pico core + + RP2040_SD is a library enable the usage of SD on RP2040-based boards + + This Library 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 Library 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 the Arduino SdFat Library. + If not, see . + + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) + + (C) Copyright 2009 by William Greiman + (C) Copyright 2010 SparkFun Electronics + (C) Copyright 2021 by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD + Licensed under GPL-3.0 license + + Version: 1.0.1 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core + 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO + *****************************************************************************************************************************/ + +#pragma once + +#ifndef Sd2PinMap_h +#define Sd2PinMap_h + +#if defined(ARDUINO_ARCH_RP2040) + + #include + + #define SS_PIN SS + #define MOSI_PIN MOSI + #define MISO_PIN MISO + #define SCK_PIN SCK + +#else + #error For RP2040 Architecture only. +#endif + +#endif // Sd2PinMap_h + diff --git a/src/utility/SdFat.h b/src/utility/SdFat.h index 2dc2411..3b57a2c 100644 --- a/src/utility/SdFat.h +++ b/src/utility/SdFat.h @@ -1,803 +1,803 @@ -/**************************************************************************************************************************** - SdFat.h - - For all RP2040 boads using Arduimo-mbed or arduino-pico core - - RP2040_SD is a library enable the usage of SD on RP2040-based boards - - This Library 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 Library 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 the Arduino SdFat Library. - If not, see . - - Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - - (C) Copyright 2009 by William Greiman - (C) Copyright 2010 SparkFun Electronics - (C) Copyright 2021 by Khoi Hoang - - Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD - Licensed under GPL-3.0 license - - Version: 1.0.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core - 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO - *****************************************************************************************************************************/ - -#pragma once - -#ifndef SdFat_h -#define SdFat_h - -/** - \file - RP2040_SdFile and RP2040_SdVolume classes -*/ - -#include "Sd2Card.h" -#include "FatStructs.h" -#include - -#include "RP2040_SD_Debug.h" - -//------------------------------------------------------------------------------ -/** - Allow use of deprecated functions if non-zero -*/ -#define ALLOW_DEPRECATED_FUNCTIONS 1 - -//------------------------------------------------------------------------------ -// forward declaration since RP2040_SdVolume is used in RP2040_SdFile -class RP2040_SdVolume; - -//============================================================================== -// RP2040_SdFile class - -#ifdef O_RDONLY - //ARDUINO_ARCH_MBED such as Arduino-mbed RP2040 core - #undef O_READ - #undef O_RDONLY - #undef O_WRITE - #undef O_WRONLY - #undef O_RDWR - #undef O_ACCMODE - #undef O_APPEND - #undef O_SYNC - #undef O_CREAT - #undef O_EXCL - #undef O_TRUNC -#endif - -// flags for ls() -enum -{ - LS_DATE = 1, - LS_SIZE = 2, - LS_R = 4 -}; - -enum -{ - O_READ = 0x01, - O_RDONLY = O_READ, - O_WRITE = 0x02, - O_WRONLY = O_WRITE, - O_RDWR = (O_READ | O_WRITE), - O_ACCMODE = (O_READ | O_WRITE), - O_APPEND = 0X04, - O_SYNC = 0X08, - O_CREAT = 0X10, - O_EXCL = 0X20, - O_TRUNC = 0X40 -}; - - -// flags for timestamp -enum -{ - T_ACCESS = 1, - T_CREATE = 2, - T_WRITE = 4 -}; - - -// values for type_ -enum -{ - FAT_FILE_TYPE_CLOSED = 0, - FAT_FILE_TYPE_NORMAL = 1, - FAT_FILE_TYPE_ROOT16 = 2, - FAT_FILE_TYPE_ROOT32 = 3, - FAT_FILE_TYPE_SUBDIR = 4, - FAT_FILE_TYPE_MIN_DIR = FAT_FILE_TYPE_ROOT16 -}; - -/** date field for FAT directory entry */ -static inline uint16_t FAT_DATE(uint16_t year, uint8_t month, uint8_t day) -{ - return (year - 1980) << 9 | month << 5 | day; -} - -/** year part of FAT directory date field */ -static inline uint16_t FAT_YEAR(uint16_t fatDate) -{ - return 1980 + (fatDate >> 9); -} - -/** month part of FAT directory date field */ -static inline uint8_t FAT_MONTH(uint16_t fatDate) -{ - return (fatDate >> 5) & 0XF; -} - -/** day part of FAT directory date field */ -static inline uint8_t FAT_DAY(uint16_t fatDate) -{ - return fatDate & 0X1F; -} - -/** time field for FAT directory entry */ -static inline uint16_t FAT_TIME(uint8_t hour, uint8_t minute, uint8_t second) -{ - return hour << 11 | minute << 5 | second >> 1; -} - -/** hour part of FAT directory time field */ -static inline uint8_t FAT_HOUR(uint16_t fatTime) -{ - return fatTime >> 11; -} - -/** minute part of FAT directory time field */ -static inline uint8_t FAT_MINUTE(uint16_t fatTime) -{ - return (fatTime >> 5) & 0X3F; -} -/** second part of FAT directory time field */ -static inline uint8_t FAT_SECOND(uint16_t fatTime) -{ - return 2 * (fatTime & 0X1F); -} - -/** Default date for file timestamps is 1 Jan 2000 */ -#define FAT_DEFAULT_DATE ( ((2000 - 1980) << 9) | (1 << 5) | 1 ) - -/** Default time for file timestamp is 1 am */ -#define FAT_DEFAULT_TIME ( (1 << 11) ) - -//------------------------------------------------------------------------------ -/** - \class RP2040_SdFile - \brief Access FAT16 and FAT32 files on SD and SDHC cards. -*/ -class RP2040_SdFile : public Print -{ - public: - /** Create an instance of RP2040_SdFile. */ - RP2040_SdFile() : type_(FAT_FILE_TYPE_CLOSED) {} - /** - writeError is set to true if an error occurs during a write(). - Set writeError to false before calling print() and/or write() and check - for true after calls to print() and/or write(). - */ - //bool writeError; - /** - Cancel unbuffered reads for this file. - See setUnbufferedRead() - */ - void clearUnbufferedRead() - { - flags_ &= ~F_FILE_UNBUFFERED_READ; - } - - uint8_t close(); - uint8_t contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock); - uint8_t createContiguous(RP2040_SdFile* dirFile, const char* fileName, uint32_t size); - - /** \return The current cluster number for a file or directory. */ - uint32_t curCluster()const - { - return curCluster_; - } - - /** \return The current position for a file or directory. */ - uint32_t curPosition()const - { - return curPosition_; - } - - /** - Set the date/time callback function - - \param[in] dateTime The user's call back function. The callback - function is of the form: - - \code - void dateTime(uint16_t* date, uint16_t* time) { - uint16_t year; - uint8_t month, day, hour, minute, second; - - // User gets date and time from GPS or real-time clock here - - // return date using FAT_DATE macro to format fields - * *date = FAT_DATE(year, month, day); - - // return time using FAT_TIME macro to format fields - * *time = FAT_TIME(hour, minute, second); - } - \endcode - - Sets the function that is called when a file is created or when - a file's directory entry is modified by sync(). All timestamps, - access, creation, and modify, are set when a file is created. - sync() maintains the last access date and last modify date/time. - - See the timestamp() function. - */ - static void dateTimeCallback(void (*dateTime)(uint16_t* date, uint16_t* time)) - { - dateTime_ = dateTime; - } - - /** - Cancel the date/time callback function. - */ - static void dateTimeCallbackCancel() - { - // use explicit zero since NULL is not defined for Sanguino - dateTime_ = 0; - } - - /** \return Address of the block that contains this file's directory. */ - uint32_t dirBlock()const - { - return dirBlock_; - } - - uint8_t dirEntry(dir_t* dir); - - /** \return Index of this file's directory in the block dirBlock. */ - uint8_t dirIndex()const - { - return dirIndex_; - } - - static void dirName(const dir_t& dir, char* name); - - /** \return The total number of bytes in a file or directory. */ - uint32_t fileSize()const - { - return fileSize_; - } - - /** \return The first cluster number for a file or directory. */ - uint32_t firstCluster()const - { - return firstCluster_; - } - - /** \return True if this is a RP2040_SdFile for a directory else false. */ - uint8_t isDir()const - { - return type_ >= FAT_FILE_TYPE_MIN_DIR; - } - - /** \return True if this is a RP2040_SdFile for a file else false. */ - uint8_t isFile()const - { - return type_ == FAT_FILE_TYPE_NORMAL; - } - - /** \return True if this is a RP2040_SdFile for an open file/directory else false. */ - uint8_t isOpen()const - { - return type_ != FAT_FILE_TYPE_CLOSED; - } - /** \return True if this is a RP2040_SdFile for a subdirectory else false. */ - uint8_t isSubDir()const - { - return type_ == FAT_FILE_TYPE_SUBDIR; - } - - /** \return True if this is a RP2040_SdFile for the root directory. */ - uint8_t isRoot()const - { - return type_ == FAT_FILE_TYPE_ROOT16 || type_ == FAT_FILE_TYPE_ROOT32; - } - - void ls(uint8_t flags = 0, uint8_t indent = 0); - uint8_t makeDir(RP2040_SdFile* dir, const char* dirName); - uint8_t open(RP2040_SdFile* dirFile, uint16_t index, uint8_t oflag); - uint8_t open(RP2040_SdFile* dirFile, const char* fileName, uint8_t oflag); - - uint8_t openRoot(RP2040_SdVolume* vol); - static void printDirName(const dir_t& dir, uint8_t width); - static void printFatDate(uint16_t fatDate); - static void printFatTime(uint16_t fatTime); - static void printTwoDigits(uint8_t v); - - /** - Read the next byte from a file. - - \return For success read returns the next byte in the file as an int. - If an error occurs or end of file is reached -1 is returned. - */ - int16_t read() - { - uint8_t b; - return read(&b, 1) == 1 ? b : -1; - } - - int16_t read(void* buf, uint16_t nbyte); - int8_t readDir(dir_t* dir); - static uint8_t remove(RP2040_SdFile* dirFile, const char* fileName); - uint8_t remove(); - - /** Set the file's current position to zero. */ - void rewind() - { - curPosition_ = curCluster_ = 0; - } - - uint8_t rmDir(); - uint8_t rmRfStar(); - - /** Set the files position to current position + \a pos. See seekSet(). */ - uint8_t seekCur(uint32_t pos) - { - return seekSet(curPosition_ + pos); - } - - /** - Set the files current position to end of file. Useful to position - a file for append. See seekSet(). - */ - uint8_t seekEnd() - { - return seekSet(fileSize_); - } - - uint8_t seekSet(uint32_t pos); - - /** - Use unbuffered reads to access this file. Used with Wave - Shield ISR. Used with Sd2Card::partialBlockRead() in WaveRP. - - Not recommended for normal applications. - */ - void setUnbufferedRead() - { - if (isFile()) - { - flags_ |= F_FILE_UNBUFFERED_READ; - } - } - - uint8_t timestamp(uint8_t flag, uint16_t year, uint8_t month, uint8_t day, - uint8_t hour, uint8_t minute, uint8_t second); - - uint8_t sync(uint8_t blocking = 1); - - /** Type of this RP2040_SdFile. You should use isFile() or isDir() instead of type() - if possible. - - \return The file or directory type. - */ - uint8_t type()const - { - return type_; - } - - uint8_t truncate(uint32_t size); - - /** \return Unbuffered read flag. */ - uint8_t unbufferedRead()const - { - return flags_ & F_FILE_UNBUFFERED_READ; - } - - /** \return RP2040_SdVolume that contains this file. */ - RP2040_SdVolume* volume()const - { - return vol_; - } - - size_t write(uint8_t b); - size_t write(const void* buf, uint16_t nbyte); - size_t write(const char* str); - - int availableForWrite(); - - //------------------------------------------------------------------------------ -#if ALLOW_DEPRECATED_FUNCTIONS - - // Deprecated functions - suppress cpplint warnings with NOLINT comment - /** \deprecated Use: - uint8_t RP2040_SdFile::contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock); - */ - uint8_t contiguousRange(uint32_t& bgnBlock, uint32_t& endBlock) - { - return contiguousRange(&bgnBlock, &endBlock); - } - - /** \deprecated Use: - uint8_t RP2040_SdFile::createContiguous(RP2040_SdFile* dirFile, - const char* fileName, uint32_t size) - */ - uint8_t createContiguous(RP2040_SdFile& dirFile, const char* fileName, uint32_t size) - { - return createContiguous(&dirFile, fileName, size); - } - - /** - \deprecated Use: - static void RP2040_SdFile::dateTimeCallback( - void (*dateTime)(uint16_t* date, uint16_t* time)); - */ - static void dateTimeCallback(void (*dateTime)(uint16_t& date, uint16_t& time)) - { - oldDateTime_ = dateTime; - dateTime_ = dateTime ? oldToNew : 0; - } - - /** \deprecated Use: uint8_t RP2040_SdFile::dirEntry(dir_t* dir); */ - uint8_t dirEntry(dir_t& dir) - { - return dirEntry(&dir); - } - - /** \deprecated Use: - uint8_t RP2040_SdFile::makeDir(RP2040_SdFile* dir, const char* dirName); - */ - uint8_t makeDir(RP2040_SdFile& dir, const char* dirName) - { - return makeDir(&dir, dirName); - } - /** \deprecated Use: - uint8_t RP2040_SdFile::open(RP2040_SdFile* dirFile, const char* fileName, uint8_t oflag); - */ - uint8_t open(RP2040_SdFile& dirFile, const char* fileName, uint8_t oflag) - { - return open(&dirFile, fileName, oflag); - } - - /** \deprecated Do not use in new apps */ - uint8_t open(RP2040_SdFile& dirFile, const char* fileName) - { - return open(dirFile, fileName, O_RDWR); - } - - /** \deprecated Use: - uint8_t RP2040_SdFile::open(RP2040_SdFile* dirFile, uint16_t index, uint8_t oflag); - */ - uint8_t open(RP2040_SdFile& dirFile, uint16_t index, uint8_t oflag) - { - return open(&dirFile, index, oflag); - } - - /** \deprecated Use: uint8_t RP2040_SdFile::openRoot(RP2040_SdVolume* vol); */ - uint8_t openRoot(RP2040_SdVolume& vol) - { - return openRoot(&vol); - } - - /** \deprecated Use: int8_t RP2040_SdFile::readDir(dir_t* dir); */ - int8_t readDir(dir_t& dir) - { - return readDir(&dir); - } - - /** \deprecated Use: - static uint8_t RP2040_SdFile::remove(RP2040_SdFile* dirFile, const char* fileName); - */ - static uint8_t remove(RP2040_SdFile& dirFile, const char* fileName) - { - return remove(&dirFile, fileName); - } - - //------------------------------------------------------------------------------ - // rest are private - private: - static void (*oldDateTime_)(uint16_t& date, uint16_t& time); - - static void oldToNew(uint16_t* date, uint16_t* time) - { - uint16_t d; - uint16_t t; - oldDateTime_(d, t); - *date = d; - *time = t; - } -#endif // ALLOW_DEPRECATED_FUNCTIONS - - private: - // bits defined in flags_ - // should be 0XF - static uint8_t const F_OFLAG = (O_ACCMODE | O_APPEND | O_SYNC); - - // available bits - static uint8_t const F_FILE_NON_BLOCKING_WRITE = 0X10; - - // a new cluster was added to the file - static uint8_t const F_FILE_CLUSTER_ADDED = 0X20; - - // use unbuffered SD read - static uint8_t const F_FILE_UNBUFFERED_READ = 0X40; - - // sync of directory entry required - static uint8_t const F_FILE_DIR_DIRTY = 0X80; - - // make sure F_OFLAG is ok -#if ((F_FILE_NON_BLOCKING_WRITE | F_FILE_CLUSTER_ADDED | F_FILE_UNBUFFERED_READ | F_FILE_DIR_DIRTY) & F_OFLAG) - #error flags_ bits conflict -#endif // flags_ bits - - // private data - uint8_t flags_; // See above for definition of flags_ bits - uint8_t type_; // type of file see above for values - uint32_t curCluster_; // cluster for current file position - uint32_t curPosition_; // current file position in bytes from beginning - uint32_t dirBlock_; // SD block that contains directory entry for file - uint8_t dirIndex_; // index of entry in dirBlock 0 <= dirIndex_ <= 0XF - uint32_t fileSize_; // file size in bytes - uint32_t firstCluster_; // first cluster of file - RP2040_SdVolume* vol_; // volume where file is located - - // private functions - uint8_t addCluster(); - uint8_t addDirCluster(); - dir_t* cacheDirEntry(uint8_t action); - static void (*dateTime_)(uint16_t* date, uint16_t* time); - static uint8_t make83Name(const char* str, uint8_t* name); - uint8_t openCachedEntry(uint8_t cacheIndex, uint8_t oflags); - dir_t* readDirCache(); -}; -//============================================================================== -// RP2040_SdVolume class -/** - \brief Cache for an SD data block -*/ -union cache_t -{ - /** Used to access cached file data blocks. */ - uint8_t data[512]; - /** Used to access cached FAT16 entries. */ - uint16_t fat16[256]; - /** Used to access cached FAT32 entries. */ - uint32_t fat32[128]; - /** Used to access cached directory entries. */ - dir_t dir[16]; - /** Used to access a cached MasterBoot Record. */ - mbr_t mbr; - /** Used to access to a cached FAT boot sector. */ - fbs_t fbs; -}; - -//------------------------------------------------------------------------------ -/** - \class RP2040_SdVolume - \brief Access FAT16 and FAT32 volumes on SD and SDHC cards. -*/ -class RP2040_SdVolume -{ - public: - /** Create an instance of RP2040_SdVolume */ - RP2040_SdVolume(): allocSearchStart_(2), fatType_(0) {} - - /** Clear the cache and returns a pointer to the cache. Used by the WaveRP - recorder to do raw write to the SD card. Not for normal apps. - */ - static uint8_t* cacheClear() - { - cacheFlush(); - cacheBlockNumber_ = 0XFFFFFFFF; - return cacheBuffer_.data; - } - - /** - Initialize a FAT volume. Try partition one first then try super - floppy format. - - \param[in] dev The Sd2Card where the volume is located. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. Reasons for - failure include not finding a valid partition, not finding a valid - FAT file system or an I/O error. - */ - uint8_t init(Sd2Card* dev) - { - return init(dev, 1) ? true : init(dev, 0); - } - - uint8_t init(Sd2Card* dev, uint8_t part); - - // inline functions that return volume info - /** \return The volume's cluster size in blocks. */ - uint8_t blocksPerCluster()const - { - return blocksPerCluster_; - } - - /** \return The number of blocks in one FAT. */ - uint32_t blocksPerFat() const - { - return blocksPerFat_; - } - - /** \return The total number of clusters in the volume. */ - uint32_t clusterCount()const - { - return clusterCount_; - } - - /** \return The shift count required to multiply by blocksPerCluster. */ - uint8_t clusterSizeShift()const - { - return clusterSizeShift_; - } - - /** \return The logical block number for the start of file data. */ - uint32_t dataStartBlock()const - { - return dataStartBlock_; - } - - /** \return The number of FAT structures on the volume. */ - uint8_t fatCount()const - { - return fatCount_; - } - - /** \return The logical block number for the start of the first FAT. */ - uint32_t fatStartBlock()const - { - return fatStartBlock_; - } - - /** \return The FAT type of the volume. Values are 12, 16 or 32. */ - uint8_t fatType()const - { - return fatType_; - } - - /** \return The number of entries in the root directory for FAT16 volumes. */ - uint32_t rootDirEntryCount()const - { - return rootDirEntryCount_; - } - - /** \return The logical block number for the start of the root directory - on FAT16 volumes or the first cluster number on FAT32 volumes. */ - uint32_t rootDirStart()const - { - return rootDirStart_; - } - - /** return a pointer to the Sd2Card object for this volume */ - static Sd2Card* sdCard() - { - return sdCard_; - } - - //------------------------------------------------------------------------------ -#if ALLOW_DEPRECATED_FUNCTIONS - // Deprecated functions - suppress cpplint warnings with NOLINT comment - /** \deprecated Use: uint8_t RP2040_SdVolume::init(Sd2Card* dev); */ - uint8_t init(Sd2Card& dev) - { - return init(&dev); - } - - /** \deprecated Use: uint8_t RP2040_SdVolume::init(Sd2Card* dev, uint8_t vol); */ - uint8_t init(Sd2Card& dev, uint8_t part) - { - return init(&dev, part); - } -#endif // ALLOW_DEPRECATED_FUNCTIONS - - //------------------------------------------------------------------------------ - private: - // Allow RP2040_SdFile access to RP2040_SdVolume private data. - friend class RP2040_SdFile; - - // value for action argument in cacheRawBlock to indicate read from cache - static uint8_t const CACHE_FOR_READ = 0; - // value for action argument in cacheRawBlock to indicate cache dirty - static uint8_t const CACHE_FOR_WRITE = 1; - - static cache_t cacheBuffer_; // 512 byte cache for device blocks - static uint32_t cacheBlockNumber_; // Logical number of block in the cache - static Sd2Card* sdCard_; // Sd2Card object for cache - static uint8_t cacheDirty_; // cacheFlush() will write block if true - static uint32_t cacheMirrorBlock_; // block number for mirror FAT - // - uint32_t allocSearchStart_; // start cluster for alloc search - uint8_t blocksPerCluster_; // cluster size in blocks - uint32_t blocksPerFat_; // FAT size in blocks - uint32_t clusterCount_; // clusters in one FAT - uint8_t clusterSizeShift_; // shift to convert cluster count to block count - uint32_t dataStartBlock_; // first data block number - uint8_t fatCount_; // number of FATs on volume - uint32_t fatStartBlock_; // start block for first FAT - uint8_t fatType_; // volume type (12, 16, OR 32) - uint16_t rootDirEntryCount_; // number of entries in FAT16 root dir - uint32_t rootDirStart_; // root start block for FAT16, cluster for FAT32 - //---------------------------------------------------------------------------- - - uint8_t allocContiguous(uint32_t count, uint32_t* curCluster); - - uint8_t blockOfCluster(uint32_t position) const - { - return (position >> 9) & (blocksPerCluster_ - 1); - } - - uint32_t clusterStartBlock(uint32_t cluster) const - { - return dataStartBlock_ + ((cluster - 2) << clusterSizeShift_); - } - - uint32_t blockNumber(uint32_t cluster, uint32_t position) const - { - return clusterStartBlock(cluster) + blockOfCluster(position); - } - - static uint8_t cacheFlush(uint8_t blocking = 1); - static uint8_t cacheMirrorBlockFlush(uint8_t blocking); - static uint8_t cacheRawBlock(uint32_t blockNumber, uint8_t action); - - static void cacheSetDirty() - { - cacheDirty_ |= CACHE_FOR_WRITE; - } - - static uint8_t cacheZeroBlock(uint32_t blockNumber); - uint8_t chainSize(uint32_t beginCluster, uint32_t* size) const; - uint8_t fatGet(uint32_t cluster, uint32_t* value) const; - uint8_t fatPut(uint32_t cluster, uint32_t value); - - uint8_t fatPutEOC(uint32_t cluster) - { - return fatPut(cluster, 0x0FFFFFFF); - } - - uint8_t freeChain(uint32_t cluster); - - uint8_t isEOC(uint32_t cluster) const - { - return cluster >= (fatType_ == 16 ? FAT16EOC_MIN : FAT32EOC_MIN); - } - - uint8_t readBlock(uint32_t block, uint8_t* dst) - { - return sdCard_->readBlock(block, dst); - } - - uint8_t readData(uint32_t block, uint16_t offset, uint16_t count, uint8_t* dst) - { - return sdCard_->readData(block, offset, count, dst); - } - - uint8_t writeBlock(uint32_t block, const uint8_t* dst, uint8_t blocking = 1) - { - return sdCard_->writeBlock(block, dst, blocking); - } - - uint8_t isBusy() - { - return sdCard_->isBusy(); - } - - uint8_t isCacheMirrorBlockDirty() - { - return (cacheMirrorBlock_ != 0); - } -}; -#endif // SdFat_h +/**************************************************************************************************************************** + SdFat.h + + For all RP2040 boads using Arduimo-mbed or arduino-pico core + + RP2040_SD is a library enable the usage of SD on RP2040-based boards + + This Library 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 Library 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 the Arduino SdFat Library. + If not, see . + + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) + + (C) Copyright 2009 by William Greiman + (C) Copyright 2010 SparkFun Electronics + (C) Copyright 2021 by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD + Licensed under GPL-3.0 license + + Version: 1.0.1 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core + 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO + *****************************************************************************************************************************/ + +#pragma once + +#ifndef SdFat_h +#define SdFat_h + +/** + \file + RP2040_SdFile and RP2040_SdVolume classes +*/ + +#include "Sd2Card.h" +#include "FatStructs.h" +#include + +#include "RP2040_SD_Debug.h" + +//------------------------------------------------------------------------------ +/** + Allow use of deprecated functions if non-zero +*/ +#define ALLOW_DEPRECATED_FUNCTIONS 1 + +//------------------------------------------------------------------------------ +// forward declaration since RP2040_SdVolume is used in RP2040_SdFile +class RP2040_SdVolume; + +//============================================================================== +// RP2040_SdFile class + +#ifdef O_RDONLY + //ARDUINO_ARCH_MBED such as Arduino-mbed RP2040 core + #undef O_READ + #undef O_RDONLY + #undef O_WRITE + #undef O_WRONLY + #undef O_RDWR + #undef O_ACCMODE + #undef O_APPEND + #undef O_SYNC + #undef O_CREAT + #undef O_EXCL + #undef O_TRUNC +#endif + +// flags for ls() +enum +{ + LS_DATE = 1, + LS_SIZE = 2, + LS_R = 4 +}; + +enum +{ + O_READ = 0x01, + O_RDONLY = O_READ, + O_WRITE = 0x02, + O_WRONLY = O_WRITE, + O_RDWR = (O_READ | O_WRITE), + O_ACCMODE = (O_READ | O_WRITE), + O_APPEND = 0X04, + O_SYNC = 0X08, + O_CREAT = 0X10, + O_EXCL = 0X20, + O_TRUNC = 0X40 +}; + + +// flags for timestamp +enum +{ + T_ACCESS = 1, + T_CREATE = 2, + T_WRITE = 4 +}; + + +// values for type_ +enum +{ + FAT_FILE_TYPE_CLOSED = 0, + FAT_FILE_TYPE_NORMAL = 1, + FAT_FILE_TYPE_ROOT16 = 2, + FAT_FILE_TYPE_ROOT32 = 3, + FAT_FILE_TYPE_SUBDIR = 4, + FAT_FILE_TYPE_MIN_DIR = FAT_FILE_TYPE_ROOT16 +}; + +/** date field for FAT directory entry */ +static inline uint16_t FAT_DATE(uint16_t year, uint8_t month, uint8_t day) +{ + return (year - 1980) << 9 | month << 5 | day; +} + +/** year part of FAT directory date field */ +static inline uint16_t FAT_YEAR(uint16_t fatDate) +{ + return 1980 + (fatDate >> 9); +} + +/** month part of FAT directory date field */ +static inline uint8_t FAT_MONTH(uint16_t fatDate) +{ + return (fatDate >> 5) & 0XF; +} + +/** day part of FAT directory date field */ +static inline uint8_t FAT_DAY(uint16_t fatDate) +{ + return fatDate & 0X1F; +} + +/** time field for FAT directory entry */ +static inline uint16_t FAT_TIME(uint8_t hour, uint8_t minute, uint8_t second) +{ + return hour << 11 | minute << 5 | second >> 1; +} + +/** hour part of FAT directory time field */ +static inline uint8_t FAT_HOUR(uint16_t fatTime) +{ + return fatTime >> 11; +} + +/** minute part of FAT directory time field */ +static inline uint8_t FAT_MINUTE(uint16_t fatTime) +{ + return (fatTime >> 5) & 0X3F; +} +/** second part of FAT directory time field */ +static inline uint8_t FAT_SECOND(uint16_t fatTime) +{ + return 2 * (fatTime & 0X1F); +} + +/** Default date for file timestamps is 1 Jan 2000 */ +#define FAT_DEFAULT_DATE ( ((2000 - 1980) << 9) | (1 << 5) | 1 ) + +/** Default time for file timestamp is 1 am */ +#define FAT_DEFAULT_TIME ( (1 << 11) ) + +//------------------------------------------------------------------------------ +/** + \class RP2040_SdFile + \brief Access FAT16 and FAT32 files on SD and SDHC cards. +*/ +class RP2040_SdFile : public Print +{ + public: + /** Create an instance of RP2040_SdFile. */ + RP2040_SdFile() : type_(FAT_FILE_TYPE_CLOSED) {} + /** + writeError is set to true if an error occurs during a write(). + Set writeError to false before calling print() and/or write() and check + for true after calls to print() and/or write(). + */ + //bool writeError; + /** + Cancel unbuffered reads for this file. + See setUnbufferedRead() + */ + void clearUnbufferedRead() + { + flags_ &= ~F_FILE_UNBUFFERED_READ; + } + + uint8_t close(); + uint8_t contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock); + uint8_t createContiguous(RP2040_SdFile* dirFile, const char* fileName, uint32_t size); + + /** \return The current cluster number for a file or directory. */ + uint32_t curCluster()const + { + return curCluster_; + } + + /** \return The current position for a file or directory. */ + uint32_t curPosition()const + { + return curPosition_; + } + + /** + Set the date/time callback function + + \param[in] dateTime The user's call back function. The callback + function is of the form: + + \code + void dateTime(uint16_t* date, uint16_t* time) { + uint16_t year; + uint8_t month, day, hour, minute, second; + + // User gets date and time from GPS or real-time clock here + + // return date using FAT_DATE macro to format fields + * *date = FAT_DATE(year, month, day); + + // return time using FAT_TIME macro to format fields + * *time = FAT_TIME(hour, minute, second); + } + \endcode + + Sets the function that is called when a file is created or when + a file's directory entry is modified by sync(). All timestamps, + access, creation, and modify, are set when a file is created. + sync() maintains the last access date and last modify date/time. + + See the timestamp() function. + */ + static void dateTimeCallback(void (*dateTime)(uint16_t* date, uint16_t* time)) + { + dateTime_ = dateTime; + } + + /** + Cancel the date/time callback function. + */ + static void dateTimeCallbackCancel() + { + // use explicit zero since NULL is not defined for Sanguino + dateTime_ = 0; + } + + /** \return Address of the block that contains this file's directory. */ + uint32_t dirBlock()const + { + return dirBlock_; + } + + uint8_t dirEntry(dir_t* dir); + + /** \return Index of this file's directory in the block dirBlock. */ + uint8_t dirIndex()const + { + return dirIndex_; + } + + static void dirName(const dir_t& dir, char* name); + + /** \return The total number of bytes in a file or directory. */ + uint32_t fileSize()const + { + return fileSize_; + } + + /** \return The first cluster number for a file or directory. */ + uint32_t firstCluster()const + { + return firstCluster_; + } + + /** \return True if this is a RP2040_SdFile for a directory else false. */ + uint8_t isDir()const + { + return type_ >= FAT_FILE_TYPE_MIN_DIR; + } + + /** \return True if this is a RP2040_SdFile for a file else false. */ + uint8_t isFile()const + { + return type_ == FAT_FILE_TYPE_NORMAL; + } + + /** \return True if this is a RP2040_SdFile for an open file/directory else false. */ + uint8_t isOpen()const + { + return type_ != FAT_FILE_TYPE_CLOSED; + } + /** \return True if this is a RP2040_SdFile for a subdirectory else false. */ + uint8_t isSubDir()const + { + return type_ == FAT_FILE_TYPE_SUBDIR; + } + + /** \return True if this is a RP2040_SdFile for the root directory. */ + uint8_t isRoot()const + { + return type_ == FAT_FILE_TYPE_ROOT16 || type_ == FAT_FILE_TYPE_ROOT32; + } + + void ls(uint8_t flags = 0, uint8_t indent = 0); + uint8_t makeDir(RP2040_SdFile* dir, const char* dirName); + uint8_t open(RP2040_SdFile* dirFile, uint16_t index, uint8_t oflag); + uint8_t open(RP2040_SdFile* dirFile, const char* fileName, uint8_t oflag); + + uint8_t openRoot(RP2040_SdVolume* vol); + static void printDirName(const dir_t& dir, uint8_t width); + static void printFatDate(uint16_t fatDate); + static void printFatTime(uint16_t fatTime); + static void printTwoDigits(uint8_t v); + + /** + Read the next byte from a file. + + \return For success read returns the next byte in the file as an int. + If an error occurs or end of file is reached -1 is returned. + */ + int16_t read() + { + uint8_t b; + return read(&b, 1) == 1 ? b : -1; + } + + int16_t read(void* buf, uint16_t nbyte); + int8_t readDir(dir_t* dir); + static uint8_t remove(RP2040_SdFile* dirFile, const char* fileName); + uint8_t remove(); + + /** Set the file's current position to zero. */ + void rewind() + { + curPosition_ = curCluster_ = 0; + } + + uint8_t rmDir(); + uint8_t rmRfStar(); + + /** Set the files position to current position + \a pos. See seekSet(). */ + uint8_t seekCur(uint32_t pos) + { + return seekSet(curPosition_ + pos); + } + + /** + Set the files current position to end of file. Useful to position + a file for append. See seekSet(). + */ + uint8_t seekEnd() + { + return seekSet(fileSize_); + } + + uint8_t seekSet(uint32_t pos); + + /** + Use unbuffered reads to access this file. Used with Wave + Shield ISR. Used with Sd2Card::partialBlockRead() in WaveRP. + + Not recommended for normal applications. + */ + void setUnbufferedRead() + { + if (isFile()) + { + flags_ |= F_FILE_UNBUFFERED_READ; + } + } + + uint8_t timestamp(uint8_t flag, uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t second); + + uint8_t sync(uint8_t blocking = 1); + + /** Type of this RP2040_SdFile. You should use isFile() or isDir() instead of type() + if possible. + + \return The file or directory type. + */ + uint8_t type()const + { + return type_; + } + + uint8_t truncate(uint32_t size); + + /** \return Unbuffered read flag. */ + uint8_t unbufferedRead()const + { + return flags_ & F_FILE_UNBUFFERED_READ; + } + + /** \return RP2040_SdVolume that contains this file. */ + RP2040_SdVolume* volume()const + { + return vol_; + } + + size_t write(uint8_t b); + size_t write(const void* buf, uint16_t nbyte); + size_t write(const char* str); + + int availableForWrite(); + + //------------------------------------------------------------------------------ +#if ALLOW_DEPRECATED_FUNCTIONS + + // Deprecated functions - suppress cpplint warnings with NOLINT comment + /** \deprecated Use: + uint8_t RP2040_SdFile::contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock); + */ + uint8_t contiguousRange(uint32_t& bgnBlock, uint32_t& endBlock) + { + return contiguousRange(&bgnBlock, &endBlock); + } + + /** \deprecated Use: + uint8_t RP2040_SdFile::createContiguous(RP2040_SdFile* dirFile, + const char* fileName, uint32_t size) + */ + uint8_t createContiguous(RP2040_SdFile& dirFile, const char* fileName, uint32_t size) + { + return createContiguous(&dirFile, fileName, size); + } + + /** + \deprecated Use: + static void RP2040_SdFile::dateTimeCallback( + void (*dateTime)(uint16_t* date, uint16_t* time)); + */ + static void dateTimeCallback(void (*dateTime)(uint16_t& date, uint16_t& time)) + { + oldDateTime_ = dateTime; + dateTime_ = dateTime ? oldToNew : 0; + } + + /** \deprecated Use: uint8_t RP2040_SdFile::dirEntry(dir_t* dir); */ + uint8_t dirEntry(dir_t& dir) + { + return dirEntry(&dir); + } + + /** \deprecated Use: + uint8_t RP2040_SdFile::makeDir(RP2040_SdFile* dir, const char* dirName); + */ + uint8_t makeDir(RP2040_SdFile& dir, const char* dirName) + { + return makeDir(&dir, dirName); + } + /** \deprecated Use: + uint8_t RP2040_SdFile::open(RP2040_SdFile* dirFile, const char* fileName, uint8_t oflag); + */ + uint8_t open(RP2040_SdFile& dirFile, const char* fileName, uint8_t oflag) + { + return open(&dirFile, fileName, oflag); + } + + /** \deprecated Do not use in new apps */ + uint8_t open(RP2040_SdFile& dirFile, const char* fileName) + { + return open(dirFile, fileName, O_RDWR); + } + + /** \deprecated Use: + uint8_t RP2040_SdFile::open(RP2040_SdFile* dirFile, uint16_t index, uint8_t oflag); + */ + uint8_t open(RP2040_SdFile& dirFile, uint16_t index, uint8_t oflag) + { + return open(&dirFile, index, oflag); + } + + /** \deprecated Use: uint8_t RP2040_SdFile::openRoot(RP2040_SdVolume* vol); */ + uint8_t openRoot(RP2040_SdVolume& vol) + { + return openRoot(&vol); + } + + /** \deprecated Use: int8_t RP2040_SdFile::readDir(dir_t* dir); */ + int8_t readDir(dir_t& dir) + { + return readDir(&dir); + } + + /** \deprecated Use: + static uint8_t RP2040_SdFile::remove(RP2040_SdFile* dirFile, const char* fileName); + */ + static uint8_t remove(RP2040_SdFile& dirFile, const char* fileName) + { + return remove(&dirFile, fileName); + } + + //------------------------------------------------------------------------------ + // rest are private + private: + static void (*oldDateTime_)(uint16_t& date, uint16_t& time); + + static void oldToNew(uint16_t* date, uint16_t* time) + { + uint16_t d; + uint16_t t; + oldDateTime_(d, t); + *date = d; + *time = t; + } +#endif // ALLOW_DEPRECATED_FUNCTIONS + + private: + // bits defined in flags_ + // should be 0XF + static uint8_t const F_OFLAG = (O_ACCMODE | O_APPEND | O_SYNC); + + // available bits + static uint8_t const F_FILE_NON_BLOCKING_WRITE = 0X10; + + // a new cluster was added to the file + static uint8_t const F_FILE_CLUSTER_ADDED = 0X20; + + // use unbuffered SD read + static uint8_t const F_FILE_UNBUFFERED_READ = 0X40; + + // sync of directory entry required + static uint8_t const F_FILE_DIR_DIRTY = 0X80; + + // make sure F_OFLAG is ok +#if ((F_FILE_NON_BLOCKING_WRITE | F_FILE_CLUSTER_ADDED | F_FILE_UNBUFFERED_READ | F_FILE_DIR_DIRTY) & F_OFLAG) +#error flags_ bits conflict +#endif // flags_ bits + + // private data + uint8_t flags_; // See above for definition of flags_ bits + uint8_t type_; // type of file see above for values + uint32_t curCluster_; // cluster for current file position + uint32_t curPosition_; // current file position in bytes from beginning + uint32_t dirBlock_; // SD block that contains directory entry for file + uint8_t dirIndex_; // index of entry in dirBlock 0 <= dirIndex_ <= 0XF + uint32_t fileSize_; // file size in bytes + uint32_t firstCluster_; // first cluster of file + RP2040_SdVolume* vol_; // volume where file is located + + // private functions + uint8_t addCluster(); + uint8_t addDirCluster(); + dir_t* cacheDirEntry(uint8_t action); + static void (*dateTime_)(uint16_t* date, uint16_t* time); + static uint8_t make83Name(const char* str, uint8_t* name); + uint8_t openCachedEntry(uint8_t cacheIndex, uint8_t oflags); + dir_t* readDirCache(); +}; +//============================================================================== +// RP2040_SdVolume class +/** + \brief Cache for an SD data block +*/ +union cache_t +{ + /** Used to access cached file data blocks. */ + uint8_t data[512]; + /** Used to access cached FAT16 entries. */ + uint16_t fat16[256]; + /** Used to access cached FAT32 entries. */ + uint32_t fat32[128]; + /** Used to access cached directory entries. */ + dir_t dir[16]; + /** Used to access a cached MasterBoot Record. */ + mbr_t mbr; + /** Used to access to a cached FAT boot sector. */ + fbs_t fbs; +}; + +//------------------------------------------------------------------------------ +/** + \class RP2040_SdVolume + \brief Access FAT16 and FAT32 volumes on SD and SDHC cards. +*/ +class RP2040_SdVolume +{ + public: + /** Create an instance of RP2040_SdVolume */ + RP2040_SdVolume(): allocSearchStart_(2), fatType_(0) {} + + /** Clear the cache and returns a pointer to the cache. Used by the WaveRP + recorder to do raw write to the SD card. Not for normal apps. + */ + static uint8_t* cacheClear() + { + cacheFlush(); + cacheBlockNumber_ = 0XFFFFFFFF; + return cacheBuffer_.data; + } + + /** + Initialize a FAT volume. Try partition one first then try super + floppy format. + + \param[in] dev The Sd2Card where the volume is located. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. Reasons for + failure include not finding a valid partition, not finding a valid + FAT file system or an I/O error. + */ + uint8_t init(Sd2Card* dev) + { + return init(dev, 1) ? true : init(dev, 0); + } + + uint8_t init(Sd2Card* dev, uint8_t part); + + // inline functions that return volume info + /** \return The volume's cluster size in blocks. */ + uint8_t blocksPerCluster()const + { + return blocksPerCluster_; + } + + /** \return The number of blocks in one FAT. */ + uint32_t blocksPerFat() const + { + return blocksPerFat_; + } + + /** \return The total number of clusters in the volume. */ + uint32_t clusterCount()const + { + return clusterCount_; + } + + /** \return The shift count required to multiply by blocksPerCluster. */ + uint8_t clusterSizeShift()const + { + return clusterSizeShift_; + } + + /** \return The logical block number for the start of file data. */ + uint32_t dataStartBlock()const + { + return dataStartBlock_; + } + + /** \return The number of FAT structures on the volume. */ + uint8_t fatCount()const + { + return fatCount_; + } + + /** \return The logical block number for the start of the first FAT. */ + uint32_t fatStartBlock()const + { + return fatStartBlock_; + } + + /** \return The FAT type of the volume. Values are 12, 16 or 32. */ + uint8_t fatType()const + { + return fatType_; + } + + /** \return The number of entries in the root directory for FAT16 volumes. */ + uint32_t rootDirEntryCount()const + { + return rootDirEntryCount_; + } + + /** \return The logical block number for the start of the root directory + on FAT16 volumes or the first cluster number on FAT32 volumes. */ + uint32_t rootDirStart()const + { + return rootDirStart_; + } + + /** return a pointer to the Sd2Card object for this volume */ + static Sd2Card* sdCard() + { + return sdCard_; + } + + //------------------------------------------------------------------------------ +#if ALLOW_DEPRECATED_FUNCTIONS + // Deprecated functions - suppress cpplint warnings with NOLINT comment + /** \deprecated Use: uint8_t RP2040_SdVolume::init(Sd2Card* dev); */ + uint8_t init(Sd2Card& dev) + { + return init(&dev); + } + + /** \deprecated Use: uint8_t RP2040_SdVolume::init(Sd2Card* dev, uint8_t vol); */ + uint8_t init(Sd2Card& dev, uint8_t part) + { + return init(&dev, part); + } +#endif // ALLOW_DEPRECATED_FUNCTIONS + + //------------------------------------------------------------------------------ + private: + // Allow RP2040_SdFile access to RP2040_SdVolume private data. + friend class RP2040_SdFile; + + // value for action argument in cacheRawBlock to indicate read from cache + static uint8_t const CACHE_FOR_READ = 0; + // value for action argument in cacheRawBlock to indicate cache dirty + static uint8_t const CACHE_FOR_WRITE = 1; + + static cache_t cacheBuffer_; // 512 byte cache for device blocks + static uint32_t cacheBlockNumber_; // Logical number of block in the cache + static Sd2Card* sdCard_; // Sd2Card object for cache + static uint8_t cacheDirty_; // cacheFlush() will write block if true + static uint32_t cacheMirrorBlock_; // block number for mirror FAT + // + uint32_t allocSearchStart_; // start cluster for alloc search + uint8_t blocksPerCluster_; // cluster size in blocks + uint32_t blocksPerFat_; // FAT size in blocks + uint32_t clusterCount_; // clusters in one FAT + uint8_t clusterSizeShift_; // shift to convert cluster count to block count + uint32_t dataStartBlock_; // first data block number + uint8_t fatCount_; // number of FATs on volume + uint32_t fatStartBlock_; // start block for first FAT + uint8_t fatType_; // volume type (12, 16, OR 32) + uint16_t rootDirEntryCount_; // number of entries in FAT16 root dir + uint32_t rootDirStart_; // root start block for FAT16, cluster for FAT32 + //---------------------------------------------------------------------------- + + uint8_t allocContiguous(uint32_t count, uint32_t* curCluster); + + uint8_t blockOfCluster(uint32_t position) const + { + return (position >> 9) & (blocksPerCluster_ - 1); + } + + uint32_t clusterStartBlock(uint32_t cluster) const + { + return dataStartBlock_ + ((cluster - 2) << clusterSizeShift_); + } + + uint32_t blockNumber(uint32_t cluster, uint32_t position) const + { + return clusterStartBlock(cluster) + blockOfCluster(position); + } + + static uint8_t cacheFlush(uint8_t blocking = 1); + static uint8_t cacheMirrorBlockFlush(uint8_t blocking); + static uint8_t cacheRawBlock(uint32_t blockNumber, uint8_t action); + + static void cacheSetDirty() + { + cacheDirty_ |= CACHE_FOR_WRITE; + } + + static uint8_t cacheZeroBlock(uint32_t blockNumber); + uint8_t chainSize(uint32_t beginCluster, uint32_t* size) const; + uint8_t fatGet(uint32_t cluster, uint32_t* value) const; + uint8_t fatPut(uint32_t cluster, uint32_t value); + + uint8_t fatPutEOC(uint32_t cluster) + { + return fatPut(cluster, 0x0FFFFFFF); + } + + uint8_t freeChain(uint32_t cluster); + + uint8_t isEOC(uint32_t cluster) const + { + return cluster >= (fatType_ == 16 ? FAT16EOC_MIN : FAT32EOC_MIN); + } + + uint8_t readBlock(uint32_t block, uint8_t* dst) + { + return sdCard_->readBlock(block, dst); + } + + uint8_t readData(uint32_t block, uint16_t offset, uint16_t count, uint8_t* dst) + { + return sdCard_->readData(block, offset, count, dst); + } + + uint8_t writeBlock(uint32_t block, const uint8_t* dst, uint8_t blocking = 1) + { + return sdCard_->writeBlock(block, dst, blocking); + } + + uint8_t isBusy() + { + return sdCard_->isBusy(); + } + + uint8_t isCacheMirrorBlockDirty() + { + return (cacheMirrorBlock_ != 0); + } +}; +#endif // SdFat_h diff --git a/src/utility/SdFatUtil.h b/src/utility/SdFatUtil.h index 4b4da17..568c3ed 100644 --- a/src/utility/SdFatUtil.h +++ b/src/utility/SdFatUtil.h @@ -1,65 +1,65 @@ -/**************************************************************************************************************************** - SdFatUtil.h - - For all RP2040 boads using Arduimo-mbed or arduino-pico core - - RP2040_SD is a library enable the usage of SD on RP2040-based boards - - This Library 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 Library 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 the Arduino SdFat Library. - If not, see . - - Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - - (C) Copyright 2009 by William Greiman - (C) Copyright 2010 SparkFun Electronics - (C) Copyright 2021 by Khoi Hoang - - Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD - Licensed under GPL-3.0 license - - Version: 1.0.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core - 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO - *****************************************************************************************************************************/ - -#pragma once - -#ifndef SdFatUtil_h -#define SdFatUtil_h - -#include - -#define NOINLINE __attribute__((noinline,unused)) -#define UNUSEDOK __attribute__((unused)) - -//------------------------------------------------------------------------------ -/** Return the number of bytes currently free in RAM. */ -static UNUSEDOK int FreeRam() -{ - extern int __bss_end; - extern int* __brkval; - int free_memory; - - if (reinterpret_cast(__brkval) == 0) - { - // if no heap use from end of bss section - free_memory = reinterpret_cast(&free_memory) - reinterpret_cast(&__bss_end); - } - else - { - // use from top of stack to heap - free_memory = reinterpret_cast(&free_memory) - reinterpret_cast(__brkval); - } - - return free_memory; -} -#endif // #define SdFatUtil_h +/**************************************************************************************************************************** + SdFatUtil.h + + For all RP2040 boads using Arduimo-mbed or arduino-pico core + + RP2040_SD is a library enable the usage of SD on RP2040-based boards + + This Library 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 Library 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 the Arduino SdFat Library. + If not, see . + + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) + + (C) Copyright 2009 by William Greiman + (C) Copyright 2010 SparkFun Electronics + (C) Copyright 2021 by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD + Licensed under GPL-3.0 license + + Version: 1.0.1 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core + 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO + *****************************************************************************************************************************/ + +#pragma once + +#ifndef SdFatUtil_h +#define SdFatUtil_h + +#include + +#define NOINLINE __attribute__((noinline,unused)) +#define UNUSEDOK __attribute__((unused)) + +//------------------------------------------------------------------------------ +/** Return the number of bytes currently free in RAM. */ +static UNUSEDOK int FreeRam() +{ + extern int __bss_end; + extern int* __brkval; + int free_memory; + + if (reinterpret_cast(__brkval) == 0) + { + // if no heap use from end of bss section + free_memory = reinterpret_cast(&free_memory) - reinterpret_cast(&__bss_end); + } + else + { + // use from top of stack to heap + free_memory = reinterpret_cast(&free_memory) - reinterpret_cast(__brkval); + } + + return free_memory; +} +#endif // #define SdFatUtil_h diff --git a/src/utility/SdFile.cpp b/src/utility/SdFile.cpp index d922c72..77c19ab 100644 --- a/src/utility/SdFile.cpp +++ b/src/utility/SdFile.cpp @@ -1,1886 +1,1888 @@ -/**************************************************************************************************************************** - SdFile.cpp - - For all RP2040 boads using Arduimo-mbed or arduino-pico core - - RP2040_SD is a library enable the usage of SD on RP2040-based boards - - This Library 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 Library 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 the Arduino SdFat Library. - If not, see . - - Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - - (C) Copyright 2009 by William Greiman - (C) Copyright 2010 SparkFun Electronics - (C) Copyright 2021 by Khoi Hoang - - Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD - Licensed under GPL-3.0 license - - Version: 1.0.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core - 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO - *****************************************************************************************************************************/ - -#include "SdFat.h" -#include - -//------------------------------------------------------------------------------ -// callback function for date/time -void (*RP2040_SdFile::dateTime_)(uint16_t* date, uint16_t* time) = NULL; - -#if ALLOW_DEPRECATED_FUNCTIONS - // suppress cpplint warnings with NOLINT comment - void (*RP2040_SdFile::oldDateTime_)(uint16_t& date, uint16_t& time) = NULL; -#endif // ALLOW_DEPRECATED_FUNCTIONS - -//------------------------------------------------------------------------------ -// add a cluster to a file -uint8_t RP2040_SdFile::addCluster() -{ - if (!vol_->allocContiguous(1, &curCluster_)) - { - return false; - } - - // if first cluster of file link to directory entry - if (firstCluster_ == 0) - { - firstCluster_ = curCluster_; - flags_ |= F_FILE_DIR_DIRTY; - } - - flags_ |= F_FILE_CLUSTER_ADDED; - - return true; -} - -//------------------------------------------------------------------------------ -// Add a cluster to a directory file and zero the cluster. -// return with first block of cluster in the cache -uint8_t RP2040_SdFile::addDirCluster() -{ - if (!addCluster()) - { - return false; - } - - // zero data in cluster insure first cluster is in cache - uint32_t block = vol_->clusterStartBlock(curCluster_); - - for (uint8_t i = vol_->blocksPerCluster_; i != 0; i--) - { - if (!RP2040_SdVolume::cacheZeroBlock(block + i - 1)) - { - return false; - } - } - - // Increase directory file size by cluster size - fileSize_ += 512UL << vol_->clusterSizeShift_; - - return true; -} - -//------------------------------------------------------------------------------ -// cache a file's directory entry -// return pointer to cached entry or null for failure -dir_t* RP2040_SdFile::cacheDirEntry(uint8_t action) -{ - if (!RP2040_SdVolume::cacheRawBlock(dirBlock_, action)) - { - return NULL; - } - - return RP2040_SdVolume::cacheBuffer_.dir + dirIndex_; -} - -//------------------------------------------------------------------------------ -/** - Close a file and force cached data and directory information - to be written to the storage device. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. - Reasons for failure include no file is open or an I/O error. -*/ -uint8_t RP2040_SdFile::close() -{ - if (!sync()) - { - return false; - } - - type_ = FAT_FILE_TYPE_CLOSED; - - return true; -} - -//------------------------------------------------------------------------------ -/** - Check for contiguous file and return its raw block range. - - \param[out] bgnBlock the first block address for the file. - \param[out] endBlock the last block address for the file. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. - Reasons for failure include file is not contiguous, file has zero length - or an I/O error occurred. -*/ -uint8_t RP2040_SdFile::contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock) -{ - // error if no blocks - if (firstCluster_ == 0) - { - return false; - } - - for (uint32_t c = firstCluster_; ; c++) - { - uint32_t next; - - if (!vol_->fatGet(c, &next)) - { - return false; - } - - // check for contiguous - if (next != (c + 1)) - { - // error if not end of chain - if (!vol_->isEOC(next)) - { - return false; - } - - *bgnBlock = vol_->clusterStartBlock(firstCluster_); - *endBlock = vol_->clusterStartBlock(c) + vol_->blocksPerCluster_ - 1; - - return true; - } - } -} - -//------------------------------------------------------------------------------ -/** - Create and open a new contiguous file of a specified size. - - \note This function only supports short DOS 8.3 names. - See open() for more information. - - \param[in] dirFile The directory where the file will be created. - \param[in] fileName A valid DOS 8.3 file name. - \param[in] size The desired file size. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. - Reasons for failure include \a fileName contains - an invalid DOS 8.3 file name, the FAT volume has not been initialized, - a file is already open, the file already exists, the root - directory is full or an I/O error. - -*/ -uint8_t RP2040_SdFile::createContiguous(RP2040_SdFile* dirFile, const char* fileName, uint32_t size) -{ - // don't allow zero length file - if (size == 0) - { - return false; - } - - if (!open(dirFile, fileName, O_CREAT | O_EXCL | O_RDWR)) - { - return false; - } - - // calculate number of clusters needed - uint32_t count = ((size - 1) >> (vol_->clusterSizeShift_ + 9)) + 1; - - // allocate clusters - if (!vol_->allocContiguous(count, &firstCluster_)) - { - remove(); - return false; - } - - fileSize_ = size; - - // insure sync() will update dir entry - flags_ |= F_FILE_DIR_DIRTY; - - return sync(); -} - -//------------------------------------------------------------------------------ -/** - Return a files directory entry - - \param[out] dir Location for return of the files directory entry. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. -*/ -uint8_t RP2040_SdFile::dirEntry(dir_t* dir) -{ - // make sure fields on SD are correct - if (!sync()) - { - return false; - } - - // read entry - dir_t* p = cacheDirEntry(RP2040_SdVolume::CACHE_FOR_READ); - - if (!p) - { - return false; - } - - // copy to caller's struct - memcpy(dir, p, sizeof(dir_t)); - - return true; -} -//------------------------------------------------------------------------------ -/** - Format the name field of \a dir into the 13 byte array - \a name in standard 8.3 short name format. - - \param[in] dir The directory structure containing the name. - \param[out] name A 13 byte char array for the formatted name. -*/ -void RP2040_SdFile::dirName(const dir_t& dir, char* name) -{ - uint8_t j = 0; - - for (uint8_t i = 0; i < 11; i++) - { - if (dir.name[i] == ' ') - { - continue; - } - - if (i == 8) - { - name[j++] = '.'; - } - - name[j++] = dir.name[i]; - } - - name[j] = 0; -} - -//------------------------------------------------------------------------------ -/** List directory contents to DEBUG PORT. - - \param[in] flags The inclusive OR of - - LS_DATE - %Print file modification date - - LS_SIZE - %Print file size. - - LS_R - Recursive list of subdirectories. - - \param[in] indent Amount of space before file name. Used for recursive - list to indicate subdirectory level. -*/ -void RP2040_SdFile::ls(uint8_t flags, uint8_t indent) -{ - dir_t* p; - - rewind(); - - while ((p = readDirCache())) - { - // done if past last used entry - if (p->name[0] == DIR_NAME_FREE) - { - break; - } - - // skip deleted entry and entries for . and .. - if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') - { - continue; - } - - // only list subdirectories and files - if (!DIR_IS_FILE_OR_SUBDIR(p)) - { - continue; - } - - // print any indent spaces - for (int8_t i = 0; i < indent; i++) - { - RP2040_SD_PRINT_SP; - } - - // print file name with possible blank fill - printDirName(*p, flags & (LS_DATE | LS_SIZE) ? 14 : 0); - - // print modify date/time if requested - if (flags & LS_DATE) - { - printFatDate(p->lastWriteDate); - RP2040_SD_PRINT_SP; - printFatTime(p->lastWriteTime); - } - - // print size if requested - if (!DIR_IS_SUBDIR(p) && (flags & LS_SIZE)) - { - RP2040_SD_LOG1(' ', p->fileSize); - } - - RP2040_SD_PRINTLN(); - - // list subdirectory content if requested - if ((flags & LS_R) && DIR_IS_SUBDIR(p)) - { - uint16_t index = curPosition() / 32 - 1; - RP2040_SdFile s; - - if (s.open(this, index, O_READ)) - { - s.ls(flags, indent + 2); - } - - seekSet(32 * (index + 1)); - } - } -} - -//------------------------------------------------------------------------------ -// format directory name field from a 8.3 name string -uint8_t RP2040_SdFile::make83Name(const char* str, uint8_t* name) -{ - uint8_t c; - uint8_t n = 7; // max index for part before dot - uint8_t i = 0; - - // blank fill name and extension - while (i < 11) - { - name[i++] = ' '; - } - - i = 0; - - while ((c = *str++) != '\0') - { - if (c == '.') - { - if (n == 10) - { - return false; // only one dot allowed - } - - n = 10; // max index for full 8.3 name - i = 8; // place for extension - } - else - { - // illegal FAT characters - uint8_t b; - - const uint8_t valid[] = "|<>^+=?/[];,*\"\\"; - const uint8_t *p = valid; - - while ((b = *p++)) - { - if (b == c) - { - return false; - } - } - - // check size and only allow ASCII printable characters - if (i > n || c < 0X21 || c > 0X7E) - { - return false; - } - - // only upper case allowed in 8.3 names - convert lower to upper - name[i++] = c < 'a' || c > 'z' ? c : c + ('A' - 'a'); - } - } - - // must have a file name, extension is optional - return name[0] != ' '; -} - -//------------------------------------------------------------------------------ -/** Make a new directory. - - \param[in] dir An open SdFat instance for the directory that will containing - the new directory. - - \param[in] dirName A valid 8.3 DOS name for the new directory. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. - Reasons for failure include this RP2040_SdFile is already open, \a dir is not a - directory, \a dirName is invalid or already exists in \a dir. -*/ -uint8_t RP2040_SdFile::makeDir(RP2040_SdFile* dir, const char* dirName) -{ - dir_t d; - - // create a normal file - if (!open(dir, dirName, O_CREAT | O_EXCL | O_RDWR)) - { - return false; - } - - // convert RP2040_SdFile to directory - flags_ = O_READ; - type_ = FAT_FILE_TYPE_SUBDIR; - - // allocate and zero first cluster - if (!addDirCluster()) - { - return false; - } - - // force entry to SD - if (!sync()) - { - return false; - } - - // cache entry - should already be in cache due to sync() call - dir_t* p = cacheDirEntry(RP2040_SdVolume::CACHE_FOR_WRITE); - - if (!p) - { - return false; - } - - // change directory entry attribute - p->attributes = DIR_ATT_DIRECTORY; - - // make entry for '.' - memcpy(&d, p, sizeof(d)); - - for (uint8_t i = 1; i < 11; i++) - { - d.name[i] = ' '; - } - - d.name[0] = '.'; - - // cache block for '.' and '..' - uint32_t block = vol_->clusterStartBlock(firstCluster_); - - if (!RP2040_SdVolume::cacheRawBlock(block, RP2040_SdVolume::CACHE_FOR_WRITE)) - { - return false; - } - - // copy '.' to block - memcpy(&RP2040_SdVolume::cacheBuffer_.dir[0], &d, sizeof(d)); - - // make entry for '..' - d.name[1] = '.'; - - if (dir->isRoot()) - { - d.firstClusterLow = 0; - d.firstClusterHigh = 0; - } - else - { - d.firstClusterLow = dir->firstCluster_ & 0XFFFF; - d.firstClusterHigh = dir->firstCluster_ >> 16; - } - - // copy '..' to block - memcpy(&RP2040_SdVolume::cacheBuffer_.dir[1], &d, sizeof(d)); - - // set position after '..' - curPosition_ = 2 * sizeof(d); - - // write first block - return RP2040_SdVolume::cacheFlush(); -} -//------------------------------------------------------------------------------ -/** - Open a file or directory by name. - - \param[in] dirFile An open SdFat instance for the directory containing the - file to be opened. - - \param[in] fileName A valid 8.3 DOS name for a file to be opened. - - \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive - OR of flags from the following list - - O_READ - Open for reading. - - O_RDONLY - Same as O_READ. - - O_WRITE - Open for writing. - - O_WRONLY - Same as O_WRITE. - - O_RDWR - Open for reading and writing. - - O_APPEND - If set, the file offset shall be set to the end of the - file prior to each write. - - O_CREAT - If the file exists, this flag has no effect except as noted - under O_EXCL below. Otherwise, the file shall be created - - O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file exists. - - O_SYNC - Call sync() after each write. This flag should not be used with - write(uint8_t), write_P(PGM_P), writeln_P(PGM_P), or the Arduino Print class. - These functions do character at a time writes so sync() will be called - after each byte. - - O_TRUNC - If the file exists and is a regular file, and the file is - successfully opened and is not read only, its length shall be truncated to 0. - - \note Directory files must be opened read only. Write and truncation is - not allowed for directory files. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. - Reasons for failure include this RP2040_SdFile is already open, \a difFile is not - a directory, \a fileName is invalid, the file does not exist - or can't be opened in the access mode specified by oflag. -*/ -uint8_t RP2040_SdFile::open(RP2040_SdFile* dirFile, const char* fileName, uint8_t oflag) -{ - uint8_t dname[11]; - dir_t* p; - - // error if already open - if (isOpen()) - { - return false; - } - - if (!make83Name(fileName, dname)) - { - return false; - } - - vol_ = dirFile->vol_; - dirFile->rewind(); - - // bool for empty entry found - uint8_t emptyFound = false; - - // search for file - while (dirFile->curPosition_ < dirFile->fileSize_) - { - uint8_t index = 0XF & (dirFile->curPosition_ >> 5); - p = dirFile->readDirCache(); - - if (p == NULL) - { - return false; - } - - if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) - { - // remember first empty slot - if (!emptyFound) - { - emptyFound = true; - dirIndex_ = index; - dirBlock_ = RP2040_SdVolume::cacheBlockNumber_; - } - - // done if no entries follow - if (p->name[0] == DIR_NAME_FREE) - { - break; - } - } - else if (!memcmp(dname, p->name, 11)) - { - // don't open existing file if O_CREAT and O_EXCL - if ((oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) - { - return false; - } - - // open found file - return openCachedEntry(0XF & index, oflag); - } - } - - // only create file if O_CREAT and O_WRITE - if ((oflag & (O_CREAT | O_WRITE)) != (O_CREAT | O_WRITE)) - { - return false; - } - - // cache found slot or add cluster if end of file - if (emptyFound) - { - p = cacheDirEntry(RP2040_SdVolume::CACHE_FOR_WRITE); - - if (!p) - { - return false; - } - } - else - { - if (dirFile->type_ == FAT_FILE_TYPE_ROOT16) - { - return false; - } - - // add and zero cluster for dirFile - first cluster is in cache for write - if (!dirFile->addDirCluster()) - { - return false; - } - - // use first entry in cluster - dirIndex_ = 0; - p = RP2040_SdVolume::cacheBuffer_.dir; - } - - // initialize as empty file - memset(p, 0, sizeof(dir_t)); - memcpy(p->name, dname, 11); - - // set timestamps - if (dateTime_) - { - // call user function - dateTime_(&p->creationDate, &p->creationTime); - } - else - { - // use default date/time - p->creationDate = FAT_DEFAULT_DATE; - p->creationTime = FAT_DEFAULT_TIME; - } - - p->lastAccessDate = p->creationDate; - p->lastWriteDate = p->creationDate; - p->lastWriteTime = p->creationTime; - - // force write of entry to SD - if (!RP2040_SdVolume::cacheFlush()) - { - return false; - } - - // open entry in cache - return openCachedEntry(dirIndex_, oflag); -} -//------------------------------------------------------------------------------ -/** - Open a file by index. - - \param[in] dirFile An open SdFat instance for the directory. - - \param[in] index The \a index of the directory entry for the file to be - opened. The value for \a index is (directory file position)/32. - - \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive - OR of flags O_READ, O_WRITE, O_TRUNC, and O_SYNC. - - See open() by fileName for definition of flags and return values. - -*/ -uint8_t RP2040_SdFile::open(RP2040_SdFile* dirFile, uint16_t index, uint8_t oflag) -{ - // error if already open - if (isOpen()) - { - return false; - } - - // don't open existing file if O_CREAT and O_EXCL - user call error - if ((oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) - { - return false; - } - - vol_ = dirFile->vol_; - - // seek to location of entry - if (!dirFile->seekSet(32 * index)) - { - return false; - } - - // read entry into cache - dir_t* p = dirFile->readDirCache(); - - if (p == NULL) - { - return false; - } - - // error if empty slot or '.' or '..' - if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') - { - return false; - } - - // open cached entry - return openCachedEntry(index & 0XF, oflag); -} -//------------------------------------------------------------------------------ -// open a cached directory entry. Assumes vol_ is initializes -uint8_t RP2040_SdFile::openCachedEntry(uint8_t dirIndex, uint8_t oflag) -{ - // location of entry in cache - dir_t* p = RP2040_SdVolume::cacheBuffer_.dir + dirIndex; - - // write or truncate is an error for a directory or read-only file - if (p->attributes & (DIR_ATT_READ_ONLY | DIR_ATT_DIRECTORY)) - { - if (oflag & (O_WRITE | O_TRUNC)) - { - return false; - } - } - - // remember location of directory entry on SD - dirIndex_ = dirIndex; - dirBlock_ = RP2040_SdVolume::cacheBlockNumber_; - - // copy first cluster number for directory fields - firstCluster_ = (uint32_t)p->firstClusterHigh << 16; - firstCluster_ |= p->firstClusterLow; - - // make sure it is a normal file or subdirectory - if (DIR_IS_FILE(p)) - { - fileSize_ = p->fileSize; - type_ = FAT_FILE_TYPE_NORMAL; - } - else if (DIR_IS_SUBDIR(p)) - { - if (!vol_->chainSize(firstCluster_, &fileSize_)) - { - return false; - } - - type_ = FAT_FILE_TYPE_SUBDIR; - } - else - { - return false; - } - - // save open flags for read/write - flags_ = oflag & (O_ACCMODE | O_SYNC | O_APPEND); - - // set to start of file - curCluster_ = 0; - curPosition_ = 0; - - // truncate file to zero length if requested - if (oflag & O_TRUNC) - { - return truncate(0); - } - - return true; -} -//------------------------------------------------------------------------------ -/** - Open a volume's root directory. - - \param[in] vol The FAT volume containing the root directory to be opened. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. - Reasons for failure include the FAT volume has not been initialized - or it a FAT12 volume. -*/ -uint8_t RP2040_SdFile::openRoot(RP2040_SdVolume* vol) -{ - // error if file is already open - if (isOpen()) - { - return false; - } - - if (vol->fatType() == 16) - { - type_ = FAT_FILE_TYPE_ROOT16; - firstCluster_ = 0; - fileSize_ = 32 * vol->rootDirEntryCount(); - } - else if (vol->fatType() == 32) - { - type_ = FAT_FILE_TYPE_ROOT32; - firstCluster_ = vol->rootDirStart(); - - if (!vol->chainSize(firstCluster_, &fileSize_)) - { - return false; - } - } - else - { - // volume is not initialized or FAT12 - return false; - } - - vol_ = vol; - // read only - flags_ = O_READ; - - // set to start of file - curCluster_ = 0; - curPosition_ = 0; - - // root has no directory entry - dirBlock_ = 0; - dirIndex_ = 0; - - return true; -} -//------------------------------------------------------------------------------ -/** %Print the name field of a directory entry in 8.3 format to DEBUG PORT - - \param[in] dir The directory structure containing the name. - \param[in] width Blank fill name if length is less than \a width. -*/ -void RP2040_SdFile::printDirName(const dir_t& dir, uint8_t width) -{ - uint8_t w = 0; - - for (uint8_t i = 0; i < 11; i++) - { - if (dir.name[i] == ' ') - { - continue; - } - - if (i == 8) - { - RP2040_SD_LOG0('.'); - w++; - } - - RP2040_SD_LOG0(dir.name[i]); - - w++; - } - - if (DIR_IS_SUBDIR(&dir)) - { - RP2040_SD_LOG0('/'); - - w++; - } - - while (w < width) - { - RP2040_SD_PRINT_SP; - - w++; - } -} - -//------------------------------------------------------------------------------ -/** %Print a directory date field to Serial. - - Format is yyyy-mm-dd. - - \param[in] fatDate The date field from a directory entry. -*/ -void RP2040_SdFile::printFatDate(uint16_t fatDate) -{ - RP2040_SD_LOG1(FAT_YEAR(fatDate), '-'); - printTwoDigits(FAT_MONTH(fatDate)); - RP2040_SD_LOG0('-'); - printTwoDigits(FAT_DAY(fatDate)); -} - -//------------------------------------------------------------------------------ -/** %Print a directory time field to Serial. - - Format is hh:mm:ss. - - \param[in] fatTime The time field from a directory entry. -*/ -void RP2040_SdFile::printFatTime(uint16_t fatTime) -{ - printTwoDigits(FAT_HOUR(fatTime)); - RP2040_SD_LOG0(':'); - printTwoDigits(FAT_MINUTE(fatTime)); - RP2040_SD_LOG0(':'); - printTwoDigits(FAT_SECOND(fatTime)); -} -//------------------------------------------------------------------------------ -/** %Print a value as two digits to Serial. - - \param[in] v Value to be printed, 0 <= \a v <= 99 -*/ -void RP2040_SdFile::printTwoDigits(uint8_t v) -{ - char str[3]; - - str[0] = '0' + v / 10; - str[1] = '0' + v % 10; - str[2] = 0; - - RP2040_SD_LOG0(str); -} - -//------------------------------------------------------------------------------ -/** - Read data from a file starting at the current position. - - \param[out] buf Pointer to the location that will receive the data. - - \param[in] nbyte Maximum number of bytes to read. - - \return For success read() returns the number of bytes read. - A value less than \a nbyte, including zero, will be returned - if end of file is reached. - If an error occurs, read() returns -1. Possible errors include - read() called before a file has been opened, corrupt file system - or an I/O error occurred. -*/ -int16_t RP2040_SdFile::read(void* buf, uint16_t nbyte) -{ - uint8_t* dst = reinterpret_cast(buf); - - // error if not open or write only - if (!isOpen() || !(flags_ & O_READ)) - { - return -1; - } - - // max bytes left in file - if (nbyte > (fileSize_ - curPosition_)) - { - nbyte = fileSize_ - curPosition_; - } - - // amount left to read - uint16_t toRead = nbyte; - - while (toRead > 0) - { - uint32_t block; // raw device block number - uint16_t offset = curPosition_ & 0X1FF; // offset in block - - if (type_ == FAT_FILE_TYPE_ROOT16) - { - block = vol_->rootDirStart() + (curPosition_ >> 9); - } - else - { - uint8_t blockOfCluster = vol_->blockOfCluster(curPosition_); - - if (offset == 0 && blockOfCluster == 0) - { - // start of new cluster - if (curPosition_ == 0) - { - // use first cluster in file - curCluster_ = firstCluster_; - } - else - { - // get next cluster from FAT - if (!vol_->fatGet(curCluster_, &curCluster_)) - { - return -1; - } - } - } - - block = vol_->clusterStartBlock(curCluster_) + blockOfCluster; - } - - uint16_t n = toRead; - - // amount to be read from current block - if (n > (512 - offset)) - { - n = 512 - offset; - } - - // no buffering needed if n == 512 or user requests no buffering - if ((unbufferedRead() || n == 512) && block != RP2040_SdVolume::cacheBlockNumber_) - { - if (!vol_->readData(block, offset, n, dst)) - { - return -1; - } - - dst += n; - } - else - { - // read block to cache and copy data to caller - if (!RP2040_SdVolume::cacheRawBlock(block, RP2040_SdVolume::CACHE_FOR_READ)) - { - return -1; - } - - uint8_t* src = RP2040_SdVolume::cacheBuffer_.data + offset; - uint8_t* end = src + n; - - while (src != end) - { - *dst++ = *src++; - } - } - - curPosition_ += n; - toRead -= n; - } - - return nbyte; -} -//------------------------------------------------------------------------------ -/** - Read the next directory entry from a directory file. - - \param[out] dir The dir_t struct that will receive the data. - - \return For success readDir() returns the number of bytes read. - A value of zero will be returned if end of file is reached. - If an error occurs, readDir() returns -1. Possible errors include - readDir() called before a directory has been opened, this is not - a directory file or an I/O error occurred. -*/ -int8_t RP2040_SdFile::readDir(dir_t* dir) -{ - int8_t n; - - // if not a directory file or miss-positioned return an error - if (!isDir() || (0X1F & curPosition_)) - { - return -1; - } - - while ((n = read(dir, sizeof(dir_t))) == sizeof(dir_t)) - { - // last entry if DIR_NAME_FREE - if (dir->name[0] == DIR_NAME_FREE) - { - break; - } - - // skip empty entries and entry for . and .. - if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') - { - continue; - } - - // return if normal file or subdirectory - if (DIR_IS_FILE_OR_SUBDIR(dir)) - { - return n; - } - } - - // error, end of file, or past last entry - return n < 0 ? -1 : 0; -} -//------------------------------------------------------------------------------ -// Read next directory entry into the cache -// Assumes file is correctly positioned -dir_t* RP2040_SdFile::readDirCache() -{ - // error if not directory - if (!isDir()) - { - return NULL; - } - - // index of entry in cache - uint8_t i = (curPosition_ >> 5) & 0XF; - - // use read to locate and cache block - if (read() < 0) - { - return NULL; - } - - // advance to next entry - curPosition_ += 31; - - // return pointer to entry - return (RP2040_SdVolume::cacheBuffer_.dir + i); -} -//------------------------------------------------------------------------------ -/** - Remove a file. - - The directory entry and all data for the file are deleted. - - \note This function should not be used to delete the 8.3 version of a - file that has a long name. For example if a file has the long name - "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. - Reasons for failure include the file read-only, is a directory, - or an I/O error occurred. -*/ -uint8_t RP2040_SdFile::remove() -{ - // free any clusters - will fail if read-only or directory - if (!truncate(0)) - { - return false; - } - - // cache directory entry - dir_t* d = cacheDirEntry(RP2040_SdVolume::CACHE_FOR_WRITE); - - if (!d) - { - return false; - } - - // mark entry deleted - d->name[0] = DIR_NAME_DELETED; - - // set this RP2040_SdFile closed - type_ = FAT_FILE_TYPE_CLOSED; - - // write entry to SD - return RP2040_SdVolume::cacheFlush(); -} -//------------------------------------------------------------------------------ -/** - Remove a file. - - The directory entry and all data for the file are deleted. - - \param[in] dirFile The directory that contains the file. - \param[in] fileName The name of the file to be removed. - - \note This function should not be used to delete the 8.3 version of a - file that has a long name. For example if a file has the long name - "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. - Reasons for failure include the file is a directory, is read only, - \a dirFile is not a directory, \a fileName is not found - or an I/O error occurred. -*/ -uint8_t RP2040_SdFile::remove(RP2040_SdFile* dirFile, const char* fileName) -{ - RP2040_SdFile file; - - if (!file.open(dirFile, fileName, O_WRITE)) - { - return false; - } - - return file.remove(); -} -//------------------------------------------------------------------------------ -/** Remove a directory file. - - The directory file will be removed only if it is empty and is not the - root directory. rmDir() follows DOS and Windows and ignores the - read-only attribute for the directory. - - \note This function should not be used to delete the 8.3 version of a - directory that has a long name. For example if a directory has the - long name "New folder" you should not delete the 8.3 name "NEWFOL~1". - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. - Reasons for failure include the file is not a directory, is the root - directory, is not empty, or an I/O error occurred. -*/ -uint8_t RP2040_SdFile::rmDir() -{ - // must be open subdirectory - if (!isSubDir()) - { - return false; - } - - rewind(); - - // make sure directory is empty - while (curPosition_ < fileSize_) - { - dir_t* p = readDirCache(); - - if (p == NULL) - { - return false; - } - - // done if past last used entry - if (p->name[0] == DIR_NAME_FREE) - { - break; - } - - // skip empty slot or '.' or '..' - if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') - { - continue; - } - - // error not empty - if (DIR_IS_FILE_OR_SUBDIR(p)) - { - return false; - } - } - - // convert empty directory to normal file for remove - type_ = FAT_FILE_TYPE_NORMAL; - flags_ |= O_WRITE; - - return remove(); -} -//------------------------------------------------------------------------------ -/** Recursively delete a directory and all contained files. - - This is like the Unix/Linux 'rm -rf *' if called with the root directory - hence the name. - - Warning - This will remove all contents of the directory including - subdirectories. The directory will then be removed if it is not root. - The read-only attribute for files will be ignored. - - \note This function should not be used to delete the 8.3 version of - a directory that has a long name. See remove() and rmDir(). - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. -*/ -uint8_t RP2040_SdFile::rmRfStar() -{ - rewind(); - - while (curPosition_ < fileSize_) - { - RP2040_SdFile f; - - // remember position - uint16_t index = curPosition_ / 32; - - dir_t* p = readDirCache(); - - if (!p) - { - return false; - } - - // done if past last entry - if (p->name[0] == DIR_NAME_FREE) - { - break; - } - - // skip empty slot or '.' or '..' - if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') - { - continue; - } - - // skip if part of long file name or volume label in root - if (!DIR_IS_FILE_OR_SUBDIR(p)) - { - continue; - } - - if (!f.open(this, index, O_READ)) - { - return false; - } - - if (f.isSubDir()) - { - // recursively delete - if (!f.rmRfStar()) - { - return false; - } - } - else - { - // ignore read-only - f.flags_ |= O_WRITE; - - if (!f.remove()) - { - return false; - } - } - - // position to next entry if required - if (curPosition_ != (32u * (index + 1))) - { - if (!seekSet(32u * (index + 1))) - { - return false; - } - } - } - - // don't try to delete root - if (isRoot()) - { - return true; - } - - return rmDir(); -} -//------------------------------------------------------------------------------ -/** - Sets a file's position. - - \param[in] pos The new position in bytes from the beginning of the file. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. -*/ -uint8_t RP2040_SdFile::seekSet(uint32_t pos) -{ - // error if file not open or seek past end of file - if (!isOpen() || pos > fileSize_) - { - return false; - } - - if (type_ == FAT_FILE_TYPE_ROOT16) - { - curPosition_ = pos; - return true; - } - - if (pos == 0) - { - // set position to start of file - curCluster_ = 0; - curPosition_ = 0; - return true; - } - - // calculate cluster index for cur and new position - uint32_t nCur = (curPosition_ - 1) >> (vol_->clusterSizeShift_ + 9); - uint32_t nNew = (pos - 1) >> (vol_->clusterSizeShift_ + 9); - - if (nNew < nCur || curPosition_ == 0) - { - // must follow chain from first cluster - curCluster_ = firstCluster_; - } - else - { - // advance from curPosition - nNew -= nCur; - } - - while (nNew--) - { - if (!vol_->fatGet(curCluster_, &curCluster_)) - { - return false; - } - } - - curPosition_ = pos; - - return true; -} -//------------------------------------------------------------------------------ -/** - The sync() call causes all modified data and directory fields - to be written to the storage device. - - \param[in] blocking If the sync should block until fully complete. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. - Reasons for failure include a call to sync() before a file has been - opened or an I/O error. -*/ -uint8_t RP2040_SdFile::sync(uint8_t blocking) -{ - // only allow open files and directories - if (!isOpen()) - { - return false; - } - - if (flags_ & F_FILE_DIR_DIRTY) - { - dir_t* d = cacheDirEntry(RP2040_SdVolume::CACHE_FOR_WRITE); - - if (!d) - { - return false; - } - - // do not set filesize for dir files - if (!isDir()) - { - d->fileSize = fileSize_; - } - - // update first cluster fields - d->firstClusterLow = firstCluster_ & 0XFFFF; - d->firstClusterHigh = firstCluster_ >> 16; - - // set modify time if user supplied a callback date/time function - if (dateTime_) - { - dateTime_(&d->lastWriteDate, &d->lastWriteTime); - d->lastAccessDate = d->lastWriteDate; - } - - // clear directory dirty - flags_ &= ~F_FILE_DIR_DIRTY; - } - - if (!blocking) - { - flags_ &= ~F_FILE_NON_BLOCKING_WRITE; - } - - return RP2040_SdVolume::cacheFlush(blocking); -} - -//------------------------------------------------------------------------------ -/** - Set a file's timestamps in its directory entry. - - \param[in] flags Values for \a flags are constructed by a bitwise-inclusive - OR of flags from the following list - - T_ACCESS - Set the file's last access date. - - T_CREATE - Set the file's creation date and time. - - T_WRITE - Set the file's last write/modification date and time. - - \param[in] year Valid range 1980 - 2107 inclusive. - - \param[in] month Valid range 1 - 12 inclusive. - - \param[in] day Valid range 1 - 31 inclusive. - - \param[in] hour Valid range 0 - 23 inclusive. - - \param[in] minute Valid range 0 - 59 inclusive. - - \param[in] second Valid range 0 - 59 inclusive - - \note It is possible to set an invalid date since there is no check for - the number of days in a month. - - \note - Modify and access timestamps may be overwritten if a date time callback - function has been set by dateTimeCallback(). - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. -*/ -uint8_t RP2040_SdFile::timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) -{ - if (!isOpen() || year < 1980 || year > 2107 || month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59) - { - return false; - } - - dir_t* d = cacheDirEntry(RP2040_SdVolume::CACHE_FOR_WRITE); - - if (!d) - { - return false; - } - - uint16_t dirDate = FAT_DATE(year, month, day); - uint16_t dirTime = FAT_TIME(hour, minute, second); - - if (flags & T_ACCESS) - { - d->lastAccessDate = dirDate; - } - - if (flags & T_CREATE) - { - d->creationDate = dirDate; - d->creationTime = dirTime; - // seems to be units of 1/100 second not 1/10 as Microsoft states - d->creationTimeTenths = second & 1 ? 100 : 0; - } - - if (flags & T_WRITE) - { - d->lastWriteDate = dirDate; - d->lastWriteTime = dirTime; - } - - RP2040_SdVolume::cacheSetDirty(); - - return sync(); -} -//------------------------------------------------------------------------------ -/** - Truncate a file to a specified length. The current file position - will be maintained if it is less than or equal to \a length otherwise - it will be set to end of file. - - \param[in] length The desired length for the file. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. - Reasons for failure include file is read only, file is a directory, - \a length is greater than the current file size or an I/O error occurs. -*/ -uint8_t RP2040_SdFile::truncate(uint32_t length) -{ - // error if not a normal file or read-only - if (!isFile() || !(flags_ & O_WRITE)) - { - return false; - } - - // error if length is greater than current size - if (length > fileSize_) - { - return false; - } - - // fileSize and length are zero - nothing to do - if (fileSize_ == 0) - { - return true; - } - - // remember position for seek after truncation - uint32_t newPos = curPosition_ > length ? length : curPosition_; - - // position to last cluster in truncated file - if (!seekSet(length)) - { - return false; - } - - if (length == 0) - { - // free all clusters - if (!vol_->freeChain(firstCluster_)) - { - return false; - } - - firstCluster_ = 0; - } - else - { - uint32_t toFree; - - if (!vol_->fatGet(curCluster_, &toFree)) - { - return false; - } - - if (!vol_->isEOC(toFree)) - { - // free extra clusters - if (!vol_->freeChain(toFree)) - { - return false; - } - - // current cluster is end of chain - if (!vol_->fatPutEOC(curCluster_)) - { - return false; - } - } - } - - fileSize_ = length; - - // need to update directory entry - flags_ |= F_FILE_DIR_DIRTY; - - if (!sync()) - { - return false; - } - - // set file to correct position - return seekSet(newPos); -} -//------------------------------------------------------------------------------ -/** - Write data to an open file. - - \note Data is moved to the cache but may not be written to the - storage device until sync() is called. - - \param[in] buf Pointer to the location of the data to be written. - - \param[in] nbyte Number of bytes to write. - - \return For success write() returns the number of bytes written, always - \a nbyte. If an error occurs, write() returns 0. Possible errors - include write() is called before a file has been opened, write is called - for a read-only file, device is full, a corrupt file system or an I/O error. - -*/ -size_t RP2040_SdFile::write(const void* buf, uint16_t nbyte) -{ - // convert void* to uint8_t* - must be before goto statements - const uint8_t* src = reinterpret_cast(buf); - - // number of bytes left to write - must be before goto statements - uint16_t nToWrite = nbyte; - // if blocking writes should be used - uint8_t blocking = (flags_ & F_FILE_NON_BLOCKING_WRITE) == 0x00; - - // error if not a normal file or is read-only - if (!isFile() || !(flags_ & O_WRITE)) - { - goto writeErrorReturn; - } - - // seek to end of file if append flag - if ((flags_ & O_APPEND) && curPosition_ != fileSize_) - { - if (!seekEnd()) - { - goto writeErrorReturn; - } - } - - while (nToWrite > 0) - { - uint8_t blockOfCluster = vol_->blockOfCluster(curPosition_); - uint16_t blockOffset = curPosition_ & 0X1FF; - - if (blockOfCluster == 0 && blockOffset == 0) - { - // start of new cluster - if (curCluster_ == 0) - { - if (firstCluster_ == 0) - { - // allocate first cluster of file - if (!addCluster()) - { - goto writeErrorReturn; - } - } - else - { - curCluster_ = firstCluster_; - } - } - else - { - uint32_t next; - - if (!vol_->fatGet(curCluster_, &next)) - { - return false; - } - - if (vol_->isEOC(next)) - { - // add cluster if at end of chain - if (!addCluster()) - { - goto writeErrorReturn; - } - } - else - { - curCluster_ = next; - } - } - } - - // max space in block - uint16_t n = 512 - blockOffset; - - // lesser of space and amount to write - if (n > nToWrite) - { - n = nToWrite; - } - - // block for data write - uint32_t block = vol_->clusterStartBlock(curCluster_) + blockOfCluster; - - if (n == 512) - { - // full block - don't need to use cache - // invalidate cache if block is in cache - if (RP2040_SdVolume::cacheBlockNumber_ == block) - { - RP2040_SdVolume::cacheBlockNumber_ = 0XFFFFFFFF; - } - - if (!vol_->writeBlock(block, src, blocking)) - { - goto writeErrorReturn; - } - - src += 512; - } - else - { - if (blockOffset == 0 && curPosition_ >= fileSize_) - { - // start of new block don't need to read into cache - if (!RP2040_SdVolume::cacheFlush()) - { - goto writeErrorReturn; - } - - RP2040_SdVolume::cacheBlockNumber_ = block; - RP2040_SdVolume::cacheSetDirty(); - } - else - { - // rewrite part of block - if (!RP2040_SdVolume::cacheRawBlock(block, RP2040_SdVolume::CACHE_FOR_WRITE)) - { - goto writeErrorReturn; - } - } - - uint8_t* dst = RP2040_SdVolume::cacheBuffer_.data + blockOffset; - uint8_t* end = dst + n; - - while (dst != end) - { - *dst++ = *src++; - } - } - - nToWrite -= n; - curPosition_ += n; - } - - - if (curPosition_ > fileSize_) - { - // update fileSize and insure sync will update dir entry - fileSize_ = curPosition_; - flags_ |= F_FILE_DIR_DIRTY; - } - else if (dateTime_ && nbyte) - { - // insure sync will update modified date and time - flags_ |= F_FILE_DIR_DIRTY; - } - - if (flags_ & O_SYNC) - { - if (!sync()) - { - goto writeErrorReturn; - } - } - - return nbyte; - -writeErrorReturn: - // return for write error - //writeError = true; - setWriteError(); - - return 0; -} -//------------------------------------------------------------------------------ -/** - Write a byte to a file. Required by the Arduino Print class. - - Use RP2040_SdFile::writeError to check for errors. -*/ -size_t RP2040_SdFile::write(uint8_t b) -{ - return write(&b, 1); -} -//------------------------------------------------------------------------------ -/** - Write a string to a file. Used by the Arduino Print class. - - Use RP2040_SdFile::writeError to check for errors. -*/ -size_t RP2040_SdFile::write(const char* str) -{ - return write(str, strlen(str)); -} - -//------------------------------------------------------------------------------ -/** - Check how many bytes can be written without blocking. - - \return The number of bytes that can be written without blocking. -*/ -int RP2040_SdFile::availableForWrite() -{ - if (!isFile() || !(flags_ & O_WRITE)) - { - return 0; - } - - // seek to end of file if append flag - if ((flags_ & O_APPEND) && curPosition_ != fileSize_) - { - if (!seekEnd()) - { - return 0; - } - } - - if (vol_->isBusy()) - { - return 0; - } - - if (flags_ & F_FILE_CLUSTER_ADDED) - { - // new cluster added, trigger a non-blocking sync - sync(0); - flags_ &= ~F_FILE_CLUSTER_ADDED; - return 0; - } - - if (vol_->isCacheMirrorBlockDirty()) - { - // cache mirror block is dirty, trigger a non-blocking sync - vol_->cacheMirrorBlockFlush(0); - return 0; - } - - flags_ |= F_FILE_NON_BLOCKING_WRITE; - - uint16_t blockOffset = curPosition_ & 0X1FF; - uint16_t n = 512 - blockOffset; - - return n; -} +/**************************************************************************************************************************** + SdFile.cpp + + For all RP2040 boads using Arduimo-mbed or arduino-pico core + + RP2040_SD is a library enable the usage of SD on RP2040-based boards + + This Library 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 Library 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 the Arduino SdFat Library. + If not, see . + + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) + + (C) Copyright 2009 by William Greiman + (C) Copyright 2010 SparkFun Electronics + (C) Copyright 2021 by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD + Licensed under GPL-3.0 license + + Version: 1.0.1 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core + 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO + *****************************************************************************************************************************/ + +#include "SdFat.h" +#include + +//------------------------------------------------------------------------------ +// callback function for date/time +void (*RP2040_SdFile::dateTime_)(uint16_t* date, uint16_t* time) = NULL; + +#if ALLOW_DEPRECATED_FUNCTIONS + // suppress cpplint warnings with NOLINT comment + void (*RP2040_SdFile::oldDateTime_)(uint16_t& date, uint16_t& time) = NULL; +#endif // ALLOW_DEPRECATED_FUNCTIONS + +//------------------------------------------------------------------------------ +// add a cluster to a file +uint8_t RP2040_SdFile::addCluster() +{ + if (!vol_->allocContiguous(1, &curCluster_)) + { + return false; + } + + // if first cluster of file link to directory entry + if (firstCluster_ == 0) + { + firstCluster_ = curCluster_; + flags_ |= F_FILE_DIR_DIRTY; + } + + flags_ |= F_FILE_CLUSTER_ADDED; + + return true; +} + +//------------------------------------------------------------------------------ +// Add a cluster to a directory file and zero the cluster. +// return with first block of cluster in the cache +uint8_t RP2040_SdFile::addDirCluster() +{ + if (!addCluster()) + { + return false; + } + + // zero data in cluster insure first cluster is in cache + uint32_t block = vol_->clusterStartBlock(curCluster_); + + for (uint8_t i = vol_->blocksPerCluster_; i != 0; i--) + { + if (!RP2040_SdVolume::cacheZeroBlock(block + i - 1)) + { + return false; + } + } + + // Increase directory file size by cluster size + fileSize_ += 512UL << vol_->clusterSizeShift_; + + return true; +} + +//------------------------------------------------------------------------------ +// cache a file's directory entry +// return pointer to cached entry or null for failure +dir_t* RP2040_SdFile::cacheDirEntry(uint8_t action) +{ + if (!RP2040_SdVolume::cacheRawBlock(dirBlock_, action)) + { + return NULL; + } + + return RP2040_SdVolume::cacheBuffer_.dir + dirIndex_; +} + +//------------------------------------------------------------------------------ +/** + Close a file and force cached data and directory information + to be written to the storage device. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. + Reasons for failure include no file is open or an I/O error. +*/ +uint8_t RP2040_SdFile::close() +{ + if (!sync()) + { + return false; + } + + type_ = FAT_FILE_TYPE_CLOSED; + + return true; +} + +//------------------------------------------------------------------------------ +/** + Check for contiguous file and return its raw block range. + + \param[out] bgnBlock the first block address for the file. + \param[out] endBlock the last block address for the file. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. + Reasons for failure include file is not contiguous, file has zero length + or an I/O error occurred. +*/ +uint8_t RP2040_SdFile::contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock) +{ + // error if no blocks + if (firstCluster_ == 0) + { + return false; + } + + for (uint32_t c = firstCluster_; ; c++) + { + uint32_t next; + + if (!vol_->fatGet(c, &next)) + { + return false; + } + + // check for contiguous + if (next != (c + 1)) + { + // error if not end of chain + if (!vol_->isEOC(next)) + { + return false; + } + + *bgnBlock = vol_->clusterStartBlock(firstCluster_); + *endBlock = vol_->clusterStartBlock(c) + vol_->blocksPerCluster_ - 1; + + return true; + } + } +} + +//------------------------------------------------------------------------------ +/** + Create and open a new contiguous file of a specified size. + + \note This function only supports short DOS 8.3 names. + See open() for more information. + + \param[in] dirFile The directory where the file will be created. + \param[in] fileName A valid DOS 8.3 file name. + \param[in] size The desired file size. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. + Reasons for failure include \a fileName contains + an invalid DOS 8.3 file name, the FAT volume has not been initialized, + a file is already open, the file already exists, the root + directory is full or an I/O error. + +*/ +uint8_t RP2040_SdFile::createContiguous(RP2040_SdFile* dirFile, const char* fileName, uint32_t size) +{ + // don't allow zero length file + if (size == 0) + { + return false; + } + + if (!open(dirFile, fileName, O_CREAT | O_EXCL | O_RDWR)) + { + return false; + } + + // calculate number of clusters needed + uint32_t count = ((size - 1) >> (vol_->clusterSizeShift_ + 9)) + 1; + + // allocate clusters + if (!vol_->allocContiguous(count, &firstCluster_)) + { + remove(); + return false; + } + + fileSize_ = size; + + // insure sync() will update dir entry + flags_ |= F_FILE_DIR_DIRTY; + + return sync(); +} + +//------------------------------------------------------------------------------ +/** + Return a files directory entry + + \param[out] dir Location for return of the files directory entry. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. +*/ +uint8_t RP2040_SdFile::dirEntry(dir_t* dir) +{ + // make sure fields on SD are correct + if (!sync()) + { + return false; + } + + // read entry + dir_t* p = cacheDirEntry(RP2040_SdVolume::CACHE_FOR_READ); + + if (!p) + { + return false; + } + + // copy to caller's struct + memcpy(dir, p, sizeof(dir_t)); + + return true; +} +//------------------------------------------------------------------------------ +/** + Format the name field of \a dir into the 13 byte array + \a name in standard 8.3 short name format. + + \param[in] dir The directory structure containing the name. + \param[out] name A 13 byte char array for the formatted name. +*/ +void RP2040_SdFile::dirName(const dir_t& dir, char* name) +{ + uint8_t j = 0; + + for (uint8_t i = 0; i < 11; i++) + { + if (dir.name[i] == ' ') + { + continue; + } + + if (i == 8) + { + name[j++] = '.'; + } + + name[j++] = dir.name[i]; + } + + name[j] = 0; +} + +//------------------------------------------------------------------------------ +/** List directory contents to DEBUG PORT. + + \param[in] flags The inclusive OR of + + LS_DATE - %Print file modification date + + LS_SIZE - %Print file size. + + LS_R - Recursive list of subdirectories. + + \param[in] indent Amount of space before file name. Used for recursive + list to indicate subdirectory level. +*/ +void RP2040_SdFile::ls(uint8_t flags, uint8_t indent) +{ + dir_t* p; + + rewind(); + + while ((p = readDirCache())) + { + // done if past last used entry + if (p->name[0] == DIR_NAME_FREE) + { + break; + } + + // skip deleted entry and entries for . and .. + if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') + { + continue; + } + + // only list subdirectories and files + if (!DIR_IS_FILE_OR_SUBDIR(p)) + { + continue; + } + + // print any indent spaces + for (int8_t i = 0; i < indent; i++) + { + RP2040_SD_PRINT_SP; + } + + // print file name with possible blank fill + printDirName(*p, flags & (LS_DATE | LS_SIZE) ? 14 : 0); + + // print modify date/time if requested + if (flags & LS_DATE) + { + printFatDate(p->lastWriteDate); + RP2040_SD_PRINT_SP; + printFatTime(p->lastWriteTime); + } + + // print size if requested + if (!DIR_IS_SUBDIR(p) && (flags & LS_SIZE)) + { + RP2040_SD_LOG1(' ', p->fileSize); + } + + RP2040_SD_PRINTLN(); + + // list subdirectory content if requested + if ((flags & LS_R) && DIR_IS_SUBDIR(p)) + { + uint16_t index = curPosition() / 32 - 1; + RP2040_SdFile s; + + if (s.open(this, index, O_READ)) + { + s.ls(flags, indent + 2); + } + + seekSet(32 * (index + 1)); + } + } +} + +//------------------------------------------------------------------------------ +// format directory name field from a 8.3 name string +uint8_t RP2040_SdFile::make83Name(const char* str, uint8_t* name) +{ + uint8_t c; + uint8_t n = 7; // max index for part before dot + uint8_t i = 0; + + // blank fill name and extension + while (i < 11) + { + name[i++] = ' '; + } + + i = 0; + + while ((c = *str++) != '\0') + { + if (c == '.') + { + if (n == 10) + { + return false; // only one dot allowed + } + + n = 10; // max index for full 8.3 name + i = 8; // place for extension + } + else + { + // illegal FAT characters + uint8_t b; + + const uint8_t valid[] = "|<>^+=?/[];,*\"\\"; + const uint8_t *p = valid; + + while ((b = *p++)) + { + if (b == c) + { + return false; + } + } + + // check size and only allow ASCII printable characters + if (i > n || c < 0X21 || c > 0X7E) + { + return false; + } + + // only upper case allowed in 8.3 names - convert lower to upper + name[i++] = c < 'a' || c > 'z' ? c : c + ('A' - 'a'); + } + } + + // must have a file name, extension is optional + return name[0] != ' '; +} + +//------------------------------------------------------------------------------ +/** Make a new directory. + + \param[in] dir An open SdFat instance for the directory that will containing + the new directory. + + \param[in] dirName A valid 8.3 DOS name for the new directory. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. + Reasons for failure include this RP2040_SdFile is already open, \a dir is not a + directory, \a dirName is invalid or already exists in \a dir. +*/ +uint8_t RP2040_SdFile::makeDir(RP2040_SdFile* dir, const char* dirName) +{ + dir_t d; + + // create a normal file + if (!open(dir, dirName, O_CREAT | O_EXCL | O_RDWR)) + { + return false; + } + + // convert RP2040_SdFile to directory + flags_ = O_READ; + type_ = FAT_FILE_TYPE_SUBDIR; + + // allocate and zero first cluster + if (!addDirCluster()) + { + return false; + } + + // force entry to SD + if (!sync()) + { + return false; + } + + // cache entry - should already be in cache due to sync() call + dir_t* p = cacheDirEntry(RP2040_SdVolume::CACHE_FOR_WRITE); + + if (!p) + { + return false; + } + + // change directory entry attribute + p->attributes = DIR_ATT_DIRECTORY; + + // make entry for '.' + memcpy(&d, p, sizeof(d)); + + for (uint8_t i = 1; i < 11; i++) + { + d.name[i] = ' '; + } + + d.name[0] = '.'; + + // cache block for '.' and '..' + uint32_t block = vol_->clusterStartBlock(firstCluster_); + + if (!RP2040_SdVolume::cacheRawBlock(block, RP2040_SdVolume::CACHE_FOR_WRITE)) + { + return false; + } + + // copy '.' to block + memcpy(&RP2040_SdVolume::cacheBuffer_.dir[0], &d, sizeof(d)); + + // make entry for '..' + d.name[1] = '.'; + + if (dir->isRoot()) + { + d.firstClusterLow = 0; + d.firstClusterHigh = 0; + } + else + { + d.firstClusterLow = dir->firstCluster_ & 0XFFFF; + d.firstClusterHigh = dir->firstCluster_ >> 16; + } + + // copy '..' to block + memcpy(&RP2040_SdVolume::cacheBuffer_.dir[1], &d, sizeof(d)); + + // set position after '..' + curPosition_ = 2 * sizeof(d); + + // write first block + return RP2040_SdVolume::cacheFlush(); +} +//------------------------------------------------------------------------------ +/** + Open a file or directory by name. + + \param[in] dirFile An open SdFat instance for the directory containing the + file to be opened. + + \param[in] fileName A valid 8.3 DOS name for a file to be opened. + + \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive + OR of flags from the following list + + O_READ - Open for reading. + + O_RDONLY - Same as O_READ. + + O_WRITE - Open for writing. + + O_WRONLY - Same as O_WRITE. + + O_RDWR - Open for reading and writing. + + O_APPEND - If set, the file offset shall be set to the end of the + file prior to each write. + + O_CREAT - If the file exists, this flag has no effect except as noted + under O_EXCL below. Otherwise, the file shall be created + + O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file exists. + + O_SYNC - Call sync() after each write. This flag should not be used with + write(uint8_t), write_P(PGM_P), writeln_P(PGM_P), or the Arduino Print class. + These functions do character at a time writes so sync() will be called + after each byte. + + O_TRUNC - If the file exists and is a regular file, and the file is + successfully opened and is not read only, its length shall be truncated to 0. + + \note Directory files must be opened read only. Write and truncation is + not allowed for directory files. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. + Reasons for failure include this RP2040_SdFile is already open, \a difFile is not + a directory, \a fileName is invalid, the file does not exist + or can't be opened in the access mode specified by oflag. +*/ +uint8_t RP2040_SdFile::open(RP2040_SdFile* dirFile, const char* fileName, uint8_t oflag) +{ + uint8_t dname[11]; + dir_t* p; + + // error if already open + if (isOpen()) + { + return false; + } + + if (!make83Name(fileName, dname)) + { + return false; + } + + vol_ = dirFile->vol_; + dirFile->rewind(); + + // bool for empty entry found + uint8_t emptyFound = false; + + // search for file + while (dirFile->curPosition_ < dirFile->fileSize_) + { + uint8_t index = 0XF & (dirFile->curPosition_ >> 5); + p = dirFile->readDirCache(); + + if (p == NULL) + { + return false; + } + + if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) + { + // remember first empty slot + if (!emptyFound) + { + emptyFound = true; + dirIndex_ = index; + dirBlock_ = RP2040_SdVolume::cacheBlockNumber_; + } + + // done if no entries follow + if (p->name[0] == DIR_NAME_FREE) + { + break; + } + } + else if (!memcmp(dname, p->name, 11)) + { + // don't open existing file if O_CREAT and O_EXCL + if ((oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) + { + return false; + } + + // open found file + return openCachedEntry(0XF & index, oflag); + } + } + + // only create file if O_CREAT and O_WRITE + if ((oflag & (O_CREAT | O_WRITE)) != (O_CREAT | O_WRITE)) + { + return false; + } + + // cache found slot or add cluster if end of file + if (emptyFound) + { + p = cacheDirEntry(RP2040_SdVolume::CACHE_FOR_WRITE); + + if (!p) + { + return false; + } + } + else + { + if (dirFile->type_ == FAT_FILE_TYPE_ROOT16) + { + return false; + } + + // add and zero cluster for dirFile - first cluster is in cache for write + if (!dirFile->addDirCluster()) + { + return false; + } + + // use first entry in cluster + dirIndex_ = 0; + p = RP2040_SdVolume::cacheBuffer_.dir; + } + + // initialize as empty file + memset(p, 0, sizeof(dir_t)); + memcpy(p->name, dname, 11); + + // set timestamps + if (dateTime_) + { + // call user function + dateTime_(&p->creationDate, &p->creationTime); + } + else + { + // use default date/time + p->creationDate = FAT_DEFAULT_DATE; + p->creationTime = FAT_DEFAULT_TIME; + } + + p->lastAccessDate = p->creationDate; + p->lastWriteDate = p->creationDate; + p->lastWriteTime = p->creationTime; + + // force write of entry to SD + if (!RP2040_SdVolume::cacheFlush()) + { + return false; + } + + // open entry in cache + return openCachedEntry(dirIndex_, oflag); +} +//------------------------------------------------------------------------------ +/** + Open a file by index. + + \param[in] dirFile An open SdFat instance for the directory. + + \param[in] index The \a index of the directory entry for the file to be + opened. The value for \a index is (directory file position)/32. + + \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive + OR of flags O_READ, O_WRITE, O_TRUNC, and O_SYNC. + + See open() by fileName for definition of flags and return values. + +*/ +uint8_t RP2040_SdFile::open(RP2040_SdFile* dirFile, uint16_t index, uint8_t oflag) +{ + // error if already open + if (isOpen()) + { + return false; + } + + // don't open existing file if O_CREAT and O_EXCL - user call error + if ((oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) + { + return false; + } + + vol_ = dirFile->vol_; + + // seek to location of entry + if (!dirFile->seekSet(32 * index)) + { + return false; + } + + // read entry into cache + dir_t* p = dirFile->readDirCache(); + + if (p == NULL) + { + return false; + } + + // error if empty slot or '.' or '..' + if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') + { + return false; + } + + // open cached entry + return openCachedEntry(index & 0XF, oflag); +} +//------------------------------------------------------------------------------ +// open a cached directory entry. Assumes vol_ is initializes +uint8_t RP2040_SdFile::openCachedEntry(uint8_t dirIndex, uint8_t oflag) +{ + // location of entry in cache + dir_t* p = RP2040_SdVolume::cacheBuffer_.dir + dirIndex; + + // write or truncate is an error for a directory or read-only file + if (p->attributes & (DIR_ATT_READ_ONLY | DIR_ATT_DIRECTORY)) + { + if (oflag & (O_WRITE | O_TRUNC)) + { + return false; + } + } + + // remember location of directory entry on SD + dirIndex_ = dirIndex; + dirBlock_ = RP2040_SdVolume::cacheBlockNumber_; + + // copy first cluster number for directory fields + firstCluster_ = (uint32_t)p->firstClusterHigh << 16; + firstCluster_ |= p->firstClusterLow; + + // make sure it is a normal file or subdirectory + if (DIR_IS_FILE(p)) + { + fileSize_ = p->fileSize; + type_ = FAT_FILE_TYPE_NORMAL; + } + else if (DIR_IS_SUBDIR(p)) + { + if (!vol_->chainSize(firstCluster_, &fileSize_)) + { + return false; + } + + type_ = FAT_FILE_TYPE_SUBDIR; + } + else + { + return false; + } + + // save open flags for read/write + flags_ = oflag & (O_ACCMODE | O_SYNC | O_APPEND); + + // set to start of file + curCluster_ = 0; + curPosition_ = 0; + + // truncate file to zero length if requested + if (oflag & O_TRUNC) + { + return truncate(0); + } + + return true; +} +//------------------------------------------------------------------------------ +/** + Open a volume's root directory. + + \param[in] vol The FAT volume containing the root directory to be opened. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. + Reasons for failure include the FAT volume has not been initialized + or it a FAT12 volume. +*/ +uint8_t RP2040_SdFile::openRoot(RP2040_SdVolume* vol) +{ + // error if file is already open + if (isOpen()) + { + return false; + } + + if (vol->fatType() == 16) + { + type_ = FAT_FILE_TYPE_ROOT16; + firstCluster_ = 0; + fileSize_ = 32 * vol->rootDirEntryCount(); + } + else if (vol->fatType() == 32) + { + type_ = FAT_FILE_TYPE_ROOT32; + firstCluster_ = vol->rootDirStart(); + + if (!vol->chainSize(firstCluster_, &fileSize_)) + { + return false; + } + } + else + { + // volume is not initialized or FAT12 + return false; + } + + vol_ = vol; + // read only + flags_ = O_READ; + + // set to start of file + curCluster_ = 0; + curPosition_ = 0; + + // root has no directory entry + dirBlock_ = 0; + dirIndex_ = 0; + + return true; +} +//------------------------------------------------------------------------------ +/** %Print the name field of a directory entry in 8.3 format to DEBUG PORT + + \param[in] dir The directory structure containing the name. + \param[in] width Blank fill name if length is less than \a width. +*/ +void RP2040_SdFile::printDirName(const dir_t& dir, uint8_t width) +{ + uint8_t w = 0; + + for (uint8_t i = 0; i < 11; i++) + { + if (dir.name[i] == ' ') + { + continue; + } + + if (i == 8) + { + RP2040_SD_LOG0('.'); + w++; + } + + RP2040_SD_LOG0(dir.name[i]); + + w++; + } + + if (DIR_IS_SUBDIR(&dir)) + { + RP2040_SD_LOG0('/'); + + w++; + } + + while (w < width) + { + RP2040_SD_PRINT_SP; + + w++; + } +} + +//------------------------------------------------------------------------------ +/** %Print a directory date field to Serial. + + Format is yyyy-mm-dd. + + \param[in] fatDate The date field from a directory entry. +*/ +void RP2040_SdFile::printFatDate(uint16_t fatDate) +{ + RP2040_SD_LOG1(FAT_YEAR(fatDate), '-'); + printTwoDigits(FAT_MONTH(fatDate)); + RP2040_SD_LOG0('-'); + printTwoDigits(FAT_DAY(fatDate)); +} + +//------------------------------------------------------------------------------ +/** %Print a directory time field to Serial. + + Format is hh:mm:ss. + + \param[in] fatTime The time field from a directory entry. +*/ +void RP2040_SdFile::printFatTime(uint16_t fatTime) +{ + printTwoDigits(FAT_HOUR(fatTime)); + RP2040_SD_LOG0(':'); + printTwoDigits(FAT_MINUTE(fatTime)); + RP2040_SD_LOG0(':'); + printTwoDigits(FAT_SECOND(fatTime)); +} +//------------------------------------------------------------------------------ +/** %Print a value as two digits to Serial. + + \param[in] v Value to be printed, 0 <= \a v <= 99 +*/ +void RP2040_SdFile::printTwoDigits(uint8_t v) +{ + char str[3]; + + str[0] = '0' + v / 10; + str[1] = '0' + v % 10; + str[2] = 0; + + RP2040_SD_LOG0(str); +} + +//------------------------------------------------------------------------------ +/** + Read data from a file starting at the current position. + + \param[out] buf Pointer to the location that will receive the data. + + \param[in] nbyte Maximum number of bytes to read. + + \return For success read() returns the number of bytes read. + A value less than \a nbyte, including zero, will be returned + if end of file is reached. + If an error occurs, read() returns -1. Possible errors include + read() called before a file has been opened, corrupt file system + or an I/O error occurred. +*/ +int16_t RP2040_SdFile::read(void* buf, uint16_t nbyte) +{ + uint8_t* dst = reinterpret_cast(buf); + + // error if not open or write only + if (!isOpen() || !(flags_ & O_READ)) + { + return -1; + } + + // max bytes left in file + if (nbyte > (fileSize_ - curPosition_)) + { + nbyte = fileSize_ - curPosition_; + } + + // amount left to read + uint16_t toRead = nbyte; + + while (toRead > 0) + { + uint32_t block; // raw device block number + uint16_t offset = curPosition_ & 0X1FF; // offset in block + + if (type_ == FAT_FILE_TYPE_ROOT16) + { + block = vol_->rootDirStart() + (curPosition_ >> 9); + } + else + { + uint8_t blockOfCluster = vol_->blockOfCluster(curPosition_); + + if (offset == 0 && blockOfCluster == 0) + { + // start of new cluster + if (curPosition_ == 0) + { + // use first cluster in file + curCluster_ = firstCluster_; + } + else + { + // get next cluster from FAT + if (!vol_->fatGet(curCluster_, &curCluster_)) + { + return -1; + } + } + } + + block = vol_->clusterStartBlock(curCluster_) + blockOfCluster; + } + + uint16_t n = toRead; + + // amount to be read from current block + if (n > (512 - offset)) + { + n = 512 - offset; + } + + // no buffering needed if n == 512 or user requests no buffering + if ((unbufferedRead() || n == 512) && block != RP2040_SdVolume::cacheBlockNumber_) + { + if (!vol_->readData(block, offset, n, dst)) + { + return -1; + } + + dst += n; + } + else + { + // read block to cache and copy data to caller + if (!RP2040_SdVolume::cacheRawBlock(block, RP2040_SdVolume::CACHE_FOR_READ)) + { + return -1; + } + + uint8_t* src = RP2040_SdVolume::cacheBuffer_.data + offset; + uint8_t* end = src + n; + + while (src != end) + { + *dst++ = *src++; + } + } + + curPosition_ += n; + toRead -= n; + } + + return nbyte; +} +//------------------------------------------------------------------------------ +/** + Read the next directory entry from a directory file. + + \param[out] dir The dir_t struct that will receive the data. + + \return For success readDir() returns the number of bytes read. + A value of zero will be returned if end of file is reached. + If an error occurs, readDir() returns -1. Possible errors include + readDir() called before a directory has been opened, this is not + a directory file or an I/O error occurred. +*/ +int8_t RP2040_SdFile::readDir(dir_t* dir) +{ + int8_t n; + + // if not a directory file or miss-positioned return an error + if (!isDir() || (0X1F & curPosition_)) + { + return -1; + } + + while ((n = read(dir, sizeof(dir_t))) == sizeof(dir_t)) + { + // last entry if DIR_NAME_FREE + if (dir->name[0] == DIR_NAME_FREE) + { + break; + } + + // skip empty entries and entry for . and .. + if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') + { + continue; + } + + // return if normal file or subdirectory + if (DIR_IS_FILE_OR_SUBDIR(dir)) + { + return n; + } + } + + // error, end of file, or past last entry + return n < 0 ? -1 : 0; +} +//------------------------------------------------------------------------------ +// Read next directory entry into the cache +// Assumes file is correctly positioned +dir_t* RP2040_SdFile::readDirCache() +{ + // error if not directory + if (!isDir()) + { + return NULL; + } + + // index of entry in cache + uint8_t i = (curPosition_ >> 5) & 0XF; + + // use read to locate and cache block + if (read() < 0) + { + return NULL; + } + + // advance to next entry + curPosition_ += 31; + + // return pointer to entry + return (RP2040_SdVolume::cacheBuffer_.dir + i); +} +//------------------------------------------------------------------------------ +/** + Remove a file. + + The directory entry and all data for the file are deleted. + + \note This function should not be used to delete the 8.3 version of a + file that has a long name. For example if a file has the long name + "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. + Reasons for failure include the file read-only, is a directory, + or an I/O error occurred. +*/ +uint8_t RP2040_SdFile::remove() +{ + // free any clusters - will fail if read-only or directory + if (!truncate(0)) + { + return false; + } + + // cache directory entry + dir_t* d = cacheDirEntry(RP2040_SdVolume::CACHE_FOR_WRITE); + + if (!d) + { + return false; + } + + // mark entry deleted + d->name[0] = DIR_NAME_DELETED; + + // set this RP2040_SdFile closed + type_ = FAT_FILE_TYPE_CLOSED; + + // write entry to SD + return RP2040_SdVolume::cacheFlush(); +} +//------------------------------------------------------------------------------ +/** + Remove a file. + + The directory entry and all data for the file are deleted. + + \param[in] dirFile The directory that contains the file. + \param[in] fileName The name of the file to be removed. + + \note This function should not be used to delete the 8.3 version of a + file that has a long name. For example if a file has the long name + "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. + Reasons for failure include the file is a directory, is read only, + \a dirFile is not a directory, \a fileName is not found + or an I/O error occurred. +*/ +uint8_t RP2040_SdFile::remove(RP2040_SdFile* dirFile, const char* fileName) +{ + RP2040_SdFile file; + + if (!file.open(dirFile, fileName, O_WRITE)) + { + return false; + } + + return file.remove(); +} +//------------------------------------------------------------------------------ +/** Remove a directory file. + + The directory file will be removed only if it is empty and is not the + root directory. rmDir() follows DOS and Windows and ignores the + read-only attribute for the directory. + + \note This function should not be used to delete the 8.3 version of a + directory that has a long name. For example if a directory has the + long name "New folder" you should not delete the 8.3 name "NEWFOL~1". + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. + Reasons for failure include the file is not a directory, is the root + directory, is not empty, or an I/O error occurred. +*/ +uint8_t RP2040_SdFile::rmDir() +{ + // must be open subdirectory + if (!isSubDir()) + { + return false; + } + + rewind(); + + // make sure directory is empty + while (curPosition_ < fileSize_) + { + dir_t* p = readDirCache(); + + if (p == NULL) + { + return false; + } + + // done if past last used entry + if (p->name[0] == DIR_NAME_FREE) + { + break; + } + + // skip empty slot or '.' or '..' + if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') + { + continue; + } + + // error not empty + if (DIR_IS_FILE_OR_SUBDIR(p)) + { + return false; + } + } + + // convert empty directory to normal file for remove + type_ = FAT_FILE_TYPE_NORMAL; + flags_ |= O_WRITE; + + return remove(); +} +//------------------------------------------------------------------------------ +/** Recursively delete a directory and all contained files. + + This is like the Unix/Linux 'rm -rf *' if called with the root directory + hence the name. + + Warning - This will remove all contents of the directory including + subdirectories. The directory will then be removed if it is not root. + The read-only attribute for files will be ignored. + + \note This function should not be used to delete the 8.3 version of + a directory that has a long name. See remove() and rmDir(). + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. +*/ +uint8_t RP2040_SdFile::rmRfStar() +{ + rewind(); + + while (curPosition_ < fileSize_) + { + RP2040_SdFile f; + + // remember position + uint16_t index = curPosition_ / 32; + + dir_t* p = readDirCache(); + + if (!p) + { + return false; + } + + // done if past last entry + if (p->name[0] == DIR_NAME_FREE) + { + break; + } + + // skip empty slot or '.' or '..' + if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') + { + continue; + } + + // skip if part of long file name or volume label in root + if (!DIR_IS_FILE_OR_SUBDIR(p)) + { + continue; + } + + if (!f.open(this, index, O_READ)) + { + return false; + } + + if (f.isSubDir()) + { + // recursively delete + if (!f.rmRfStar()) + { + return false; + } + } + else + { + // ignore read-only + f.flags_ |= O_WRITE; + + if (!f.remove()) + { + return false; + } + } + + // position to next entry if required + if (curPosition_ != (32u * (index + 1))) + { + if (!seekSet(32u * (index + 1))) + { + return false; + } + } + } + + // don't try to delete root + if (isRoot()) + { + return true; + } + + return rmDir(); +} +//------------------------------------------------------------------------------ +/** + Sets a file's position. + + \param[in] pos The new position in bytes from the beginning of the file. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. +*/ +uint8_t RP2040_SdFile::seekSet(uint32_t pos) +{ + // error if file not open or seek past end of file + if (!isOpen() || pos > fileSize_) + { + return false; + } + + if (type_ == FAT_FILE_TYPE_ROOT16) + { + curPosition_ = pos; + return true; + } + + if (pos == 0) + { + // set position to start of file + curCluster_ = 0; + curPosition_ = 0; + return true; + } + + // calculate cluster index for cur and new position + uint32_t nCur = (curPosition_ - 1) >> (vol_->clusterSizeShift_ + 9); + uint32_t nNew = (pos - 1) >> (vol_->clusterSizeShift_ + 9); + + if (nNew < nCur || curPosition_ == 0) + { + // must follow chain from first cluster + curCluster_ = firstCluster_; + } + else + { + // advance from curPosition + nNew -= nCur; + } + + while (nNew--) + { + if (!vol_->fatGet(curCluster_, &curCluster_)) + { + return false; + } + } + + curPosition_ = pos; + + return true; +} +//------------------------------------------------------------------------------ +/** + The sync() call causes all modified data and directory fields + to be written to the storage device. + + \param[in] blocking If the sync should block until fully complete. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. + Reasons for failure include a call to sync() before a file has been + opened or an I/O error. +*/ +uint8_t RP2040_SdFile::sync(uint8_t blocking) +{ + // only allow open files and directories + if (!isOpen()) + { + return false; + } + + if (flags_ & F_FILE_DIR_DIRTY) + { + dir_t* d = cacheDirEntry(RP2040_SdVolume::CACHE_FOR_WRITE); + + if (!d) + { + return false; + } + + // do not set filesize for dir files + if (!isDir()) + { + d->fileSize = fileSize_; + } + + // update first cluster fields + d->firstClusterLow = firstCluster_ & 0XFFFF; + d->firstClusterHigh = firstCluster_ >> 16; + + // set modify time if user supplied a callback date/time function + if (dateTime_) + { + dateTime_(&d->lastWriteDate, &d->lastWriteTime); + d->lastAccessDate = d->lastWriteDate; + } + + // clear directory dirty + flags_ &= ~F_FILE_DIR_DIRTY; + } + + if (!blocking) + { + flags_ &= ~F_FILE_NON_BLOCKING_WRITE; + } + + return RP2040_SdVolume::cacheFlush(blocking); +} + +//------------------------------------------------------------------------------ +/** + Set a file's timestamps in its directory entry. + + \param[in] flags Values for \a flags are constructed by a bitwise-inclusive + OR of flags from the following list + + T_ACCESS - Set the file's last access date. + + T_CREATE - Set the file's creation date and time. + + T_WRITE - Set the file's last write/modification date and time. + + \param[in] year Valid range 1980 - 2107 inclusive. + + \param[in] month Valid range 1 - 12 inclusive. + + \param[in] day Valid range 1 - 31 inclusive. + + \param[in] hour Valid range 0 - 23 inclusive. + + \param[in] minute Valid range 0 - 59 inclusive. + + \param[in] second Valid range 0 - 59 inclusive + + \note It is possible to set an invalid date since there is no check for + the number of days in a month. + + \note + Modify and access timestamps may be overwritten if a date time callback + function has been set by dateTimeCallback(). + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. +*/ +uint8_t RP2040_SdFile::timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, + uint8_t second) +{ + if (!isOpen() || year < 1980 || year > 2107 || month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 + || minute > 59 || second > 59) + { + return false; + } + + dir_t* d = cacheDirEntry(RP2040_SdVolume::CACHE_FOR_WRITE); + + if (!d) + { + return false; + } + + uint16_t dirDate = FAT_DATE(year, month, day); + uint16_t dirTime = FAT_TIME(hour, minute, second); + + if (flags & T_ACCESS) + { + d->lastAccessDate = dirDate; + } + + if (flags & T_CREATE) + { + d->creationDate = dirDate; + d->creationTime = dirTime; + // seems to be units of 1/100 second not 1/10 as Microsoft states + d->creationTimeTenths = second & 1 ? 100 : 0; + } + + if (flags & T_WRITE) + { + d->lastWriteDate = dirDate; + d->lastWriteTime = dirTime; + } + + RP2040_SdVolume::cacheSetDirty(); + + return sync(); +} +//------------------------------------------------------------------------------ +/** + Truncate a file to a specified length. The current file position + will be maintained if it is less than or equal to \a length otherwise + it will be set to end of file. + + \param[in] length The desired length for the file. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. + Reasons for failure include file is read only, file is a directory, + \a length is greater than the current file size or an I/O error occurs. +*/ +uint8_t RP2040_SdFile::truncate(uint32_t length) +{ + // error if not a normal file or read-only + if (!isFile() || !(flags_ & O_WRITE)) + { + return false; + } + + // error if length is greater than current size + if (length > fileSize_) + { + return false; + } + + // fileSize and length are zero - nothing to do + if (fileSize_ == 0) + { + return true; + } + + // remember position for seek after truncation + uint32_t newPos = curPosition_ > length ? length : curPosition_; + + // position to last cluster in truncated file + if (!seekSet(length)) + { + return false; + } + + if (length == 0) + { + // free all clusters + if (!vol_->freeChain(firstCluster_)) + { + return false; + } + + firstCluster_ = 0; + } + else + { + uint32_t toFree; + + if (!vol_->fatGet(curCluster_, &toFree)) + { + return false; + } + + if (!vol_->isEOC(toFree)) + { + // free extra clusters + if (!vol_->freeChain(toFree)) + { + return false; + } + + // current cluster is end of chain + if (!vol_->fatPutEOC(curCluster_)) + { + return false; + } + } + } + + fileSize_ = length; + + // need to update directory entry + flags_ |= F_FILE_DIR_DIRTY; + + if (!sync()) + { + return false; + } + + // set file to correct position + return seekSet(newPos); +} +//------------------------------------------------------------------------------ +/** + Write data to an open file. + + \note Data is moved to the cache but may not be written to the + storage device until sync() is called. + + \param[in] buf Pointer to the location of the data to be written. + + \param[in] nbyte Number of bytes to write. + + \return For success write() returns the number of bytes written, always + \a nbyte. If an error occurs, write() returns 0. Possible errors + include write() is called before a file has been opened, write is called + for a read-only file, device is full, a corrupt file system or an I/O error. + +*/ +size_t RP2040_SdFile::write(const void* buf, uint16_t nbyte) +{ + // convert void* to uint8_t* - must be before goto statements + const uint8_t* src = reinterpret_cast(buf); + + // number of bytes left to write - must be before goto statements + uint16_t nToWrite = nbyte; + // if blocking writes should be used + uint8_t blocking = (flags_ & F_FILE_NON_BLOCKING_WRITE) == 0x00; + + // error if not a normal file or is read-only + if (!isFile() || !(flags_ & O_WRITE)) + { + goto writeErrorReturn; + } + + // seek to end of file if append flag + if ((flags_ & O_APPEND) && curPosition_ != fileSize_) + { + if (!seekEnd()) + { + goto writeErrorReturn; + } + } + + while (nToWrite > 0) + { + uint8_t blockOfCluster = vol_->blockOfCluster(curPosition_); + uint16_t blockOffset = curPosition_ & 0X1FF; + + if (blockOfCluster == 0 && blockOffset == 0) + { + // start of new cluster + if (curCluster_ == 0) + { + if (firstCluster_ == 0) + { + // allocate first cluster of file + if (!addCluster()) + { + goto writeErrorReturn; + } + } + else + { + curCluster_ = firstCluster_; + } + } + else + { + uint32_t next; + + if (!vol_->fatGet(curCluster_, &next)) + { + return false; + } + + if (vol_->isEOC(next)) + { + // add cluster if at end of chain + if (!addCluster()) + { + goto writeErrorReturn; + } + } + else + { + curCluster_ = next; + } + } + } + + // max space in block + uint16_t n = 512 - blockOffset; + + // lesser of space and amount to write + if (n > nToWrite) + { + n = nToWrite; + } + + // block for data write + uint32_t block = vol_->clusterStartBlock(curCluster_) + blockOfCluster; + + if (n == 512) + { + // full block - don't need to use cache + // invalidate cache if block is in cache + if (RP2040_SdVolume::cacheBlockNumber_ == block) + { + RP2040_SdVolume::cacheBlockNumber_ = 0XFFFFFFFF; + } + + if (!vol_->writeBlock(block, src, blocking)) + { + goto writeErrorReturn; + } + + src += 512; + } + else + { + if (blockOffset == 0 && curPosition_ >= fileSize_) + { + // start of new block don't need to read into cache + if (!RP2040_SdVolume::cacheFlush()) + { + goto writeErrorReturn; + } + + RP2040_SdVolume::cacheBlockNumber_ = block; + RP2040_SdVolume::cacheSetDirty(); + } + else + { + // rewrite part of block + if (!RP2040_SdVolume::cacheRawBlock(block, RP2040_SdVolume::CACHE_FOR_WRITE)) + { + goto writeErrorReturn; + } + } + + uint8_t* dst = RP2040_SdVolume::cacheBuffer_.data + blockOffset; + uint8_t* end = dst + n; + + while (dst != end) + { + *dst++ = *src++; + } + } + + nToWrite -= n; + curPosition_ += n; + } + + + if (curPosition_ > fileSize_) + { + // update fileSize and insure sync will update dir entry + fileSize_ = curPosition_; + flags_ |= F_FILE_DIR_DIRTY; + } + else if (dateTime_ && nbyte) + { + // insure sync will update modified date and time + flags_ |= F_FILE_DIR_DIRTY; + } + + if (flags_ & O_SYNC) + { + if (!sync()) + { + goto writeErrorReturn; + } + } + + return nbyte; + +writeErrorReturn: + // return for write error + //writeError = true; + setWriteError(); + + return 0; +} +//------------------------------------------------------------------------------ +/** + Write a byte to a file. Required by the Arduino Print class. + + Use RP2040_SdFile::writeError to check for errors. +*/ +size_t RP2040_SdFile::write(uint8_t b) +{ + return write(&b, 1); +} +//------------------------------------------------------------------------------ +/** + Write a string to a file. Used by the Arduino Print class. + + Use RP2040_SdFile::writeError to check for errors. +*/ +size_t RP2040_SdFile::write(const char* str) +{ + return write(str, strlen(str)); +} + +//------------------------------------------------------------------------------ +/** + Check how many bytes can be written without blocking. + + \return The number of bytes that can be written without blocking. +*/ +int RP2040_SdFile::availableForWrite() +{ + if (!isFile() || !(flags_ & O_WRITE)) + { + return 0; + } + + // seek to end of file if append flag + if ((flags_ & O_APPEND) && curPosition_ != fileSize_) + { + if (!seekEnd()) + { + return 0; + } + } + + if (vol_->isBusy()) + { + return 0; + } + + if (flags_ & F_FILE_CLUSTER_ADDED) + { + // new cluster added, trigger a non-blocking sync + sync(0); + flags_ &= ~F_FILE_CLUSTER_ADDED; + return 0; + } + + if (vol_->isCacheMirrorBlockDirty()) + { + // cache mirror block is dirty, trigger a non-blocking sync + vol_->cacheMirrorBlockFlush(0); + return 0; + } + + flags_ |= F_FILE_NON_BLOCKING_WRITE; + + uint16_t blockOffset = curPosition_ & 0X1FF; + uint16_t n = 512 - blockOffset; + + return n; +} diff --git a/src/utility/SdInfo.h b/src/utility/SdInfo.h index b544775..c8ae375 100644 --- a/src/utility/SdInfo.h +++ b/src/utility/SdInfo.h @@ -1,246 +1,246 @@ -/**************************************************************************************************************************** - SdInfo.h - - For all RP2040 boads using Arduimo-mbed or arduino-pico core - - RP2040_SD is a library enable the usage of SD on RP2040-based boards - - This Library 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 Library 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 the Arduino SdFat Library. - If not, see . - - Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - - (C) Copyright 2009 by William Greiman - (C) Copyright 2010 SparkFun Electronics - (C) Copyright 2021 by Khoi Hoang - - Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD - Licensed under GPL-3.0 license - - Version: 1.0.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core - 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO - *****************************************************************************************************************************/ - -#pragma once - -#ifndef SdInfo_h -#define SdInfo_h - -#include - -// Based on the document: -// -// SD Specifications -// Part 1 -// Physical Layer -// Simplified Specification -// Version 2.00 -// September 25, 2006 -// -// www.sdcard.org/developers/tech/sdcard/pls/Simplified_Physical_Layer_Spec.pdf -//------------------------------------------------------------------------------ -// SD card commands - -enum -{ - CMD0 = 0x00, // GO_IDLE_STATE - init card in spi mode if CS low - CMD8 = 0x08, // SEND_IF_COND - verify SD Memory Card interface operating condition. - CMD9 = 0x09, // SEND_CSD - read the Card Specific Data (CSD register) - CMD10 = 0x0A, // SEND_CID - read the card identification information (CID register) - CMD13 = 0x0D, // SEND_STATUS - read the card status register - CMD17 = 0x11, // READ_BLOCK - read a single data block from the card - CMD24 = 0x18, // WRITE_BLOCK - write a single data block to the card - CMD25 = 0x19, // WRITE_MULTIPLE_BLOCK - write blocks of data until a STOP_TRANSMISSION - CMD32 = 0x20, // ERASE_WR_BLK_START - sets the address of the first block to be erased - CMD33 = 0x21, // ERASE_WR_BLK_END - sets the address of the last block of the continuous range to be erased - CMD38 = 0x26, // ERASE - erase all previously selected blocks - CMD55 = 0x37, // APP_CMD - escape for application specific command - CMD58 = 0x3A, // READ_OCR - read the OCR register of a card - ACMD23 = 0x17, // SET_WR_BLK_ERASE_COUNT - Set the number of write blocks to be pre-erased before writing - ACMD41 = 0x29, // SD_SEND_OP_COMD - Sends host capacity support information and activates the card's initialization process -}; - -// SD card status -enum -{ - R1_READY_STATE = 0x00, // status for card in the ready state - R1_IDLE_STATE = 0x01, // status for card in the idle state - R1_ILLEGAL_COMMAND = 0x04, // status bit for illegal command - WRITE_MULTIPLE_TOKEN = 0xFC, // start data token for write multiple blocks - STOP_TRAN_TOKEN = 0xFD, // stop token for write multiple blocks - DATA_START_BLOCK = 0xFE, // start data token for read or write single block -}; - - -/** mask for data response tokens after a write block operation */ -#define DATA_RES_MASK 0x1F - -/** write data accepted token */ -#define DATA_RES_ACCEPTED 0x05 - -//------------------------------------------------------------------------------ -typedef struct CID -{ - // byte 0 - uint8_t mid; // Manufacturer ID - // byte 1-2 - char oid[2]; // OEM/Application ID - // byte 3-7 - char pnm[5]; // Product name - // byte 8 - unsigned prv_m : 4; // Product revision n.m - unsigned prv_n : 4; - // byte 9-12 - uint32_t psn; // Product serial number - // byte 13 - unsigned mdt_year_high : 4; // Manufacturing date - unsigned reserved : 4; - // byte 14 - unsigned mdt_month : 4; - unsigned mdt_year_low : 4; - // byte 15 - unsigned always1 : 1; - unsigned crc : 7; -} cid_t; - -//------------------------------------------------------------------------------ -// CSD for version 1.00 cards -typedef struct CSDV1 -{ - // byte 0 - unsigned reserved1 : 6; - unsigned csd_ver : 2; - // byte 1 - uint8_t taac; - // byte 2 - uint8_t nsac; - // byte 3 - uint8_t tran_speed; - // byte 4 - uint8_t ccc_high; - // byte 5 - unsigned read_bl_len : 4; - unsigned ccc_low : 4; - // byte 6 - unsigned c_size_high : 2; - unsigned reserved2 : 2; - unsigned dsr_imp : 1; - unsigned read_blk_misalign : 1; - unsigned write_blk_misalign : 1; - unsigned read_bl_partial : 1; - // byte 7 - uint8_t c_size_mid; - // byte 8 - unsigned vdd_r_curr_max : 3; - unsigned vdd_r_curr_min : 3; - unsigned c_size_low : 2; - // byte 9 - unsigned c_size_mult_high : 2; - unsigned vdd_w_cur_max : 3; - unsigned vdd_w_curr_min : 3; - // byte 10 - unsigned sector_size_high : 6; - unsigned erase_blk_en : 1; - unsigned c_size_mult_low : 1; - // byte 11 - unsigned wp_grp_size : 7; - unsigned sector_size_low : 1; - // byte 12 - unsigned write_bl_len_high : 2; - unsigned r2w_factor : 3; - unsigned reserved3 : 2; - unsigned wp_grp_enable : 1; - // byte 13 - unsigned reserved4 : 5; - unsigned write_partial : 1; - unsigned write_bl_len_low : 2; - // byte 14 - unsigned reserved5: 2; - unsigned file_format : 2; - unsigned tmp_write_protect : 1; - unsigned perm_write_protect : 1; - unsigned copy : 1; - unsigned file_format_grp : 1; - // byte 15 - unsigned always1 : 1; - unsigned crc : 7; -} csd1_t; - -//------------------------------------------------------------------------------ -// CSD for version 2.00 cards -typedef struct CSDV2 -{ - // byte 0 - unsigned reserved1 : 6; - unsigned csd_ver : 2; - // byte 1 - uint8_t taac; - // byte 2 - uint8_t nsac; - // byte 3 - uint8_t tran_speed; - // byte 4 - uint8_t ccc_high; - // byte 5 - unsigned read_bl_len : 4; - unsigned ccc_low : 4; - // byte 6 - unsigned reserved2 : 4; - unsigned dsr_imp : 1; - unsigned read_blk_misalign : 1; - unsigned write_blk_misalign : 1; - unsigned read_bl_partial : 1; - // byte 7 - unsigned reserved3 : 2; - unsigned c_size_high : 6; - // byte 8 - uint8_t c_size_mid; - // byte 9 - uint8_t c_size_low; - // byte 10 - unsigned sector_size_high : 6; - unsigned erase_blk_en : 1; - unsigned reserved4 : 1; - // byte 11 - unsigned wp_grp_size : 7; - unsigned sector_size_low : 1; - // byte 12 - unsigned write_bl_len_high : 2; - unsigned r2w_factor : 3; - unsigned reserved5 : 2; - unsigned wp_grp_enable : 1; - // byte 13 - unsigned reserved6 : 5; - unsigned write_partial : 1; - unsigned write_bl_len_low : 2; - // byte 14 - unsigned reserved7: 2; - unsigned file_format : 2; - unsigned tmp_write_protect : 1; - unsigned perm_write_protect : 1; - unsigned copy : 1; - unsigned file_format_grp : 1; - // byte 15 - unsigned always1 : 1; - unsigned crc : 7; -} csd2_t; - -//------------------------------------------------------------------------------ -// union of old and new style CSD register -union csd_t -{ - csd1_t v1; - csd2_t v2; -}; - -#endif // SdInfo_h +/**************************************************************************************************************************** + SdInfo.h + + For all RP2040 boads using Arduimo-mbed or arduino-pico core + + RP2040_SD is a library enable the usage of SD on RP2040-based boards + + This Library 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 Library 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 the Arduino SdFat Library. + If not, see . + + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) + + (C) Copyright 2009 by William Greiman + (C) Copyright 2010 SparkFun Electronics + (C) Copyright 2021 by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD + Licensed under GPL-3.0 license + + Version: 1.0.1 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core + 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO + *****************************************************************************************************************************/ + +#pragma once + +#ifndef SdInfo_h +#define SdInfo_h + +#include + +// Based on the document: +// +// SD Specifications +// Part 1 +// Physical Layer +// Simplified Specification +// Version 2.00 +// September 25, 2006 +// +// www.sdcard.org/developers/tech/sdcard/pls/Simplified_Physical_Layer_Spec.pdf +//------------------------------------------------------------------------------ +// SD card commands + +enum +{ + CMD0 = 0x00, // GO_IDLE_STATE - init card in spi mode if CS low + CMD8 = 0x08, // SEND_IF_COND - verify SD Memory Card interface operating condition. + CMD9 = 0x09, // SEND_CSD - read the Card Specific Data (CSD register) + CMD10 = 0x0A, // SEND_CID - read the card identification information (CID register) + CMD13 = 0x0D, // SEND_STATUS - read the card status register + CMD17 = 0x11, // READ_BLOCK - read a single data block from the card + CMD24 = 0x18, // WRITE_BLOCK - write a single data block to the card + CMD25 = 0x19, // WRITE_MULTIPLE_BLOCK - write blocks of data until a STOP_TRANSMISSION + CMD32 = 0x20, // ERASE_WR_BLK_START - sets the address of the first block to be erased + CMD33 = 0x21, // ERASE_WR_BLK_END - sets the address of the last block of the continuous range to be erased + CMD38 = 0x26, // ERASE - erase all previously selected blocks + CMD55 = 0x37, // APP_CMD - escape for application specific command + CMD58 = 0x3A, // READ_OCR - read the OCR register of a card + ACMD23 = 0x17, // SET_WR_BLK_ERASE_COUNT - Set the number of write blocks to be pre-erased before writing + ACMD41 = 0x29, // SD_SEND_OP_COMD - Sends host capacity support information and activates the card's initialization process +}; + +// SD card status +enum +{ + R1_READY_STATE = 0x00, // status for card in the ready state + R1_IDLE_STATE = 0x01, // status for card in the idle state + R1_ILLEGAL_COMMAND = 0x04, // status bit for illegal command + WRITE_MULTIPLE_TOKEN = 0xFC, // start data token for write multiple blocks + STOP_TRAN_TOKEN = 0xFD, // stop token for write multiple blocks + DATA_START_BLOCK = 0xFE, // start data token for read or write single block +}; + + +/** mask for data response tokens after a write block operation */ +#define DATA_RES_MASK 0x1F + +/** write data accepted token */ +#define DATA_RES_ACCEPTED 0x05 + +//------------------------------------------------------------------------------ +typedef struct CID +{ + // byte 0 + uint8_t mid; // Manufacturer ID + // byte 1-2 + char oid[2]; // OEM/Application ID + // byte 3-7 + char pnm[5]; // Product name + // byte 8 + unsigned prv_m : 4; // Product revision n.m + unsigned prv_n : 4; + // byte 9-12 + uint32_t psn; // Product serial number + // byte 13 + unsigned mdt_year_high : 4; // Manufacturing date + unsigned reserved : 4; + // byte 14 + unsigned mdt_month : 4; + unsigned mdt_year_low : 4; + // byte 15 + unsigned always1 : 1; + unsigned crc : 7; +} cid_t; + +//------------------------------------------------------------------------------ +// CSD for version 1.00 cards +typedef struct CSDV1 +{ + // byte 0 + unsigned reserved1 : 6; + unsigned csd_ver : 2; + // byte 1 + uint8_t taac; + // byte 2 + uint8_t nsac; + // byte 3 + uint8_t tran_speed; + // byte 4 + uint8_t ccc_high; + // byte 5 + unsigned read_bl_len : 4; + unsigned ccc_low : 4; + // byte 6 + unsigned c_size_high : 2; + unsigned reserved2 : 2; + unsigned dsr_imp : 1; + unsigned read_blk_misalign : 1; + unsigned write_blk_misalign : 1; + unsigned read_bl_partial : 1; + // byte 7 + uint8_t c_size_mid; + // byte 8 + unsigned vdd_r_curr_max : 3; + unsigned vdd_r_curr_min : 3; + unsigned c_size_low : 2; + // byte 9 + unsigned c_size_mult_high : 2; + unsigned vdd_w_cur_max : 3; + unsigned vdd_w_curr_min : 3; + // byte 10 + unsigned sector_size_high : 6; + unsigned erase_blk_en : 1; + unsigned c_size_mult_low : 1; + // byte 11 + unsigned wp_grp_size : 7; + unsigned sector_size_low : 1; + // byte 12 + unsigned write_bl_len_high : 2; + unsigned r2w_factor : 3; + unsigned reserved3 : 2; + unsigned wp_grp_enable : 1; + // byte 13 + unsigned reserved4 : 5; + unsigned write_partial : 1; + unsigned write_bl_len_low : 2; + // byte 14 + unsigned reserved5: 2; + unsigned file_format : 2; + unsigned tmp_write_protect : 1; + unsigned perm_write_protect : 1; + unsigned copy : 1; + unsigned file_format_grp : 1; + // byte 15 + unsigned always1 : 1; + unsigned crc : 7; +} csd1_t; + +//------------------------------------------------------------------------------ +// CSD for version 2.00 cards +typedef struct CSDV2 +{ + // byte 0 + unsigned reserved1 : 6; + unsigned csd_ver : 2; + // byte 1 + uint8_t taac; + // byte 2 + uint8_t nsac; + // byte 3 + uint8_t tran_speed; + // byte 4 + uint8_t ccc_high; + // byte 5 + unsigned read_bl_len : 4; + unsigned ccc_low : 4; + // byte 6 + unsigned reserved2 : 4; + unsigned dsr_imp : 1; + unsigned read_blk_misalign : 1; + unsigned write_blk_misalign : 1; + unsigned read_bl_partial : 1; + // byte 7 + unsigned reserved3 : 2; + unsigned c_size_high : 6; + // byte 8 + uint8_t c_size_mid; + // byte 9 + uint8_t c_size_low; + // byte 10 + unsigned sector_size_high : 6; + unsigned erase_blk_en : 1; + unsigned reserved4 : 1; + // byte 11 + unsigned wp_grp_size : 7; + unsigned sector_size_low : 1; + // byte 12 + unsigned write_bl_len_high : 2; + unsigned r2w_factor : 3; + unsigned reserved5 : 2; + unsigned wp_grp_enable : 1; + // byte 13 + unsigned reserved6 : 5; + unsigned write_partial : 1; + unsigned write_bl_len_low : 2; + // byte 14 + unsigned reserved7: 2; + unsigned file_format : 2; + unsigned tmp_write_protect : 1; + unsigned perm_write_protect : 1; + unsigned copy : 1; + unsigned file_format_grp : 1; + // byte 15 + unsigned always1 : 1; + unsigned crc : 7; +} csd2_t; + +//------------------------------------------------------------------------------ +// union of old and new style CSD register +union csd_t +{ + csd1_t v1; + csd2_t v2; +}; + +#endif // SdInfo_h diff --git a/src/utility/SdVolume.cpp b/src/utility/SdVolume.cpp index 48cab43..1c8ea2b 100644 --- a/src/utility/SdVolume.cpp +++ b/src/utility/SdVolume.cpp @@ -1,471 +1,472 @@ -/**************************************************************************************************************************** - SdVolume.cpp - - For all RP2040 boads using Arduimo-mbed or arduino-pico core - - RP2040_SD is a library enable the usage of SD on RP2040-based boards - - This Library 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 Library 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 the Arduino SdFat Library. - If not, see . - - Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) - - (C) Copyright 2009 by William Greiman - (C) Copyright 2010 SparkFun Electronics - (C) Copyright 2021 by Khoi Hoang - - Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD - Licensed under GPL-3.0 license - - Version: 1.0.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core - 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO - *****************************************************************************************************************************/ - -#include "SdFat.h" - -//------------------------------------------------------------------------------ -// raw block cache -// init cacheBlockNumber_to invalid SD block number -uint32_t RP2040_SdVolume::cacheBlockNumber_ = 0XFFFFFFFF; -cache_t RP2040_SdVolume::cacheBuffer_; // 512 byte cache for Sd2Card -Sd2Card* RP2040_SdVolume::sdCard_; // pointer to SD card object -uint8_t RP2040_SdVolume::cacheDirty_ = 0; // cacheFlush() will write block if true -uint32_t RP2040_SdVolume::cacheMirrorBlock_ = 0; // mirror block for second FAT - -//------------------------------------------------------------------------------ -// find a contiguous group of clusters -uint8_t RP2040_SdVolume::allocContiguous(uint32_t count, uint32_t* curCluster) -{ - // start of group - uint32_t bgnCluster; - - // flag to save place to start next search - uint8_t setStart; - - // set search start cluster - if (*curCluster) - { - // try to make file contiguous - bgnCluster = *curCluster + 1; - - // don't save new start location - setStart = false; - } - else - { - // start at likely place for free cluster - bgnCluster = allocSearchStart_; - - // save next search start if one cluster - setStart = 1 == count; - } - - // end of group - uint32_t endCluster = bgnCluster; - - // last cluster of FAT - uint32_t fatEnd = clusterCount_ + 1; - - // search the FAT for free clusters - for (uint32_t n = 0;; n++, endCluster++) - { - // can't find space checked all clusters - if (n >= clusterCount_) - { - return false; - } - - // past end - start from beginning of FAT - if (endCluster > fatEnd) - { - bgnCluster = endCluster = 2; - } - - uint32_t f; - - if (!fatGet(endCluster, &f)) - { - return false; - } - - if (f != 0) - { - // cluster in use try next cluster as bgnCluster - bgnCluster = endCluster + 1; - } - else if ((endCluster - bgnCluster + 1) == count) - { - // done - found space - break; - } - } - - // mark end of chain - if (!fatPutEOC(endCluster)) - { - return false; - } - - // link clusters - while (endCluster > bgnCluster) - { - if (!fatPut(endCluster - 1, endCluster)) - { - return false; - } - - endCluster--; - } - - if (*curCluster != 0) - { - // connect chains - if (!fatPut(*curCluster, bgnCluster)) - { - return false; - } - } - - // return first cluster number to caller - *curCluster = bgnCluster; - - // remember possible next free cluster - if (setStart) - { - allocSearchStart_ = bgnCluster + 1; - } - - return true; -} -//------------------------------------------------------------------------------ -uint8_t RP2040_SdVolume::cacheFlush(uint8_t blocking) -{ - if (cacheDirty_) - { - if (!sdCard_->writeBlock(cacheBlockNumber_, cacheBuffer_.data, blocking)) - { - return false; - } - - if (!blocking) - { - return true; - } - - // mirror FAT tables - if (!cacheMirrorBlockFlush(blocking)) - { - return false; - } - - cacheDirty_ = 0; - } - - return true; -} -//------------------------------------------------------------------------------ -uint8_t RP2040_SdVolume::cacheMirrorBlockFlush(uint8_t blocking) -{ - if (cacheMirrorBlock_) - { - if (!sdCard_->writeBlock(cacheMirrorBlock_, cacheBuffer_.data, blocking)) - { - return false; - } - - cacheMirrorBlock_ = 0; - } - - return true; -} -//------------------------------------------------------------------------------ -uint8_t RP2040_SdVolume::cacheRawBlock(uint32_t blockNumber, uint8_t action) -{ - if (cacheBlockNumber_ != blockNumber) - { - if (!cacheFlush()) - { - return false; - } - - if (!sdCard_->readBlock(blockNumber, cacheBuffer_.data)) - { - return false; - } - - cacheBlockNumber_ = blockNumber; - } - - cacheDirty_ |= action; - - return true; -} -//------------------------------------------------------------------------------ -// cache a zero block for blockNumber -uint8_t RP2040_SdVolume::cacheZeroBlock(uint32_t blockNumber) -{ - if (!cacheFlush()) - { - return false; - } - - // loop take less flash than memset(cacheBuffer_.data, 0, 512); - for (uint16_t i = 0; i < 512; i++) - { - cacheBuffer_.data[i] = 0; - } - - cacheBlockNumber_ = blockNumber; - cacheSetDirty(); - - return true; -} -//------------------------------------------------------------------------------ -// return the size in bytes of a cluster chain -uint8_t RP2040_SdVolume::chainSize(uint32_t cluster, uint32_t* size) const -{ - uint32_t s = 0; - - do - { - if (!fatGet(cluster, &cluster)) - { - return false; - } - - s += 512UL << clusterSizeShift_; - } while (!isEOC(cluster)); - - *size = s; - return true; -} - -//------------------------------------------------------------------------------ -// Fetch a FAT entry -uint8_t RP2040_SdVolume::fatGet(uint32_t cluster, uint32_t* value) const -{ - if (cluster > (clusterCount_ + 1)) - { - return false; - } - - uint32_t lba = fatStartBlock_; - lba += fatType_ == 16 ? cluster >> 8 : cluster >> 7; - - if (lba != cacheBlockNumber_) - { - if (!cacheRawBlock(lba, CACHE_FOR_READ)) - { - return false; - } - } - - if (fatType_ == 16) - { - *value = cacheBuffer_.fat16[cluster & 0XFF]; - } - else - { - *value = cacheBuffer_.fat32[cluster & 0X7F] & FAT32MASK; - } - - return true; -} - -//------------------------------------------------------------------------------ -// Store a FAT entry -uint8_t RP2040_SdVolume::fatPut(uint32_t cluster, uint32_t value) -{ - // error if reserved cluster - if (cluster < 2) - { - return false; - } - - // error if not in FAT - if (cluster > (clusterCount_ + 1)) - { - return false; - } - - // calculate block address for entry - uint32_t lba = fatStartBlock_; - lba += fatType_ == 16 ? cluster >> 8 : cluster >> 7; - - if (lba != cacheBlockNumber_) - { - if (!cacheRawBlock(lba, CACHE_FOR_READ)) - { - return false; - } - } - - // store entry - if (fatType_ == 16) - { - cacheBuffer_.fat16[cluster & 0XFF] = value; - } - else - { - cacheBuffer_.fat32[cluster & 0X7F] = value; - } - - cacheSetDirty(); - - // mirror second FAT - if (fatCount_ > 1) - { - cacheMirrorBlock_ = lba + blocksPerFat_; - } - - return true; -} -//------------------------------------------------------------------------------ -// free a cluster chain -uint8_t RP2040_SdVolume::freeChain(uint32_t cluster) -{ - // clear free cluster location - allocSearchStart_ = 2; - - do - { - uint32_t next; - - if (!fatGet(cluster, &next)) - { - return false; - } - - // free cluster - if (!fatPut(cluster, 0)) - { - return false; - } - - cluster = next; - } while (!isEOC(cluster)); - - return true; -} -//------------------------------------------------------------------------------ -/** - Initialize a FAT volume. - - \param[in] dev The SD card where the volume is located. - - \param[in] part The partition to be used. Legal values for \a part are - 1-4 to use the corresponding partition on a device formatted with - a MBR, Master Boot Record, or zero if the device is formatted as - a super floppy with the FAT boot sector in block zero. - - \return The value one, true, is returned for success and - the value zero, false, is returned for failure. Reasons for - failure include not finding a valid partition, not finding a valid - FAT file system in the specified partition or an I/O error. -*/ -uint8_t RP2040_SdVolume::init(Sd2Card* dev, uint8_t part) -{ - uint32_t volumeStartBlock = 0; - sdCard_ = dev; - - // if part == 0 assume super floppy with FAT boot sector in block zero - // if part > 0 assume mbr volume with partition table - if (part) - { - if (part > 4) - { - return false; - } - - if (!cacheRawBlock(volumeStartBlock, CACHE_FOR_READ)) - { - return false; - } - - part_t* p = &cacheBuffer_.mbr.part[part - 1]; - - if ((p->boot & 0X7F) != 0 || p->totalSectors < 100 || p->firstSector == 0) - { - // not a valid partition - return false; - } - volumeStartBlock = p->firstSector; - } - - if (!cacheRawBlock(volumeStartBlock, CACHE_FOR_READ)) - { - return false; - } - - bpb_t* bpb = &cacheBuffer_.fbs.bpb; - - if (bpb->bytesPerSector != 512 || bpb->fatCount == 0 || bpb->reservedSectorCount == 0 || bpb->sectorsPerCluster == 0) - { - // not valid FAT volume - return false; - } - - fatCount_ = bpb->fatCount; - blocksPerCluster_ = bpb->sectorsPerCluster; - - // determine shift that is same as multiply by blocksPerCluster_ - clusterSizeShift_ = 0; - - while (blocksPerCluster_ != (1 << clusterSizeShift_)) - { - // error if not power of 2 - if (clusterSizeShift_++ > 7) - { - return false; - } - } - - blocksPerFat_ = bpb->sectorsPerFat16 ? bpb->sectorsPerFat16 : bpb->sectorsPerFat32; - - fatStartBlock_ = volumeStartBlock + bpb->reservedSectorCount; - - // count for FAT16 zero for FAT32 - rootDirEntryCount_ = bpb->rootDirEntryCount; - - // directory start for FAT16 dataStart for FAT32 - rootDirStart_ = fatStartBlock_ + bpb->fatCount * blocksPerFat_; - - // data start for FAT16 and FAT32 - dataStartBlock_ = rootDirStart_ + ((32 * bpb->rootDirEntryCount + 511) / 512); - - // total blocks for FAT16 or FAT32 - uint32_t totalBlocks = bpb->totalSectors16 ? - bpb->totalSectors16 : bpb->totalSectors32; - // total data blocks - clusterCount_ = totalBlocks - (dataStartBlock_ - volumeStartBlock); - - // divide by cluster size to get cluster count - clusterCount_ >>= clusterSizeShift_; - - // FAT type is determined by cluster count - if (clusterCount_ < 4085) - { - fatType_ = 12; - } - else if (clusterCount_ < 65525) - { - fatType_ = 16; - } - else - { - rootDirStart_ = bpb->fat32RootCluster; - fatType_ = 32; - } - - return true; -} +/**************************************************************************************************************************** + SdVolume.cpp + + For all RP2040 boads using Arduimo-mbed or arduino-pico core + + RP2040_SD is a library enable the usage of SD on RP2040-based boards + + This Library 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 Library 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 the Arduino SdFat Library. + If not, see . + + Based on and modified from Arduino SdFat Library (https://github.com/arduino/Arduino) + + (C) Copyright 2009 by William Greiman + (C) Copyright 2010 SparkFun Electronics + (C) Copyright 2021 by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/RP2040_SD + Licensed under GPL-3.0 license + + Version: 1.0.1 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 18/06/2021 Port to RP2040-based boards using Arduimo-mbed or arduino-pico core + 1.0.1 K Hoang 22/10/2021 Fix platform in library.json for PIO + *****************************************************************************************************************************/ + +#include "SdFat.h" + +//------------------------------------------------------------------------------ +// raw block cache +// init cacheBlockNumber_to invalid SD block number +uint32_t RP2040_SdVolume::cacheBlockNumber_ = 0XFFFFFFFF; +cache_t RP2040_SdVolume::cacheBuffer_; // 512 byte cache for Sd2Card +Sd2Card* RP2040_SdVolume::sdCard_; // pointer to SD card object +uint8_t RP2040_SdVolume::cacheDirty_ = 0; // cacheFlush() will write block if true +uint32_t RP2040_SdVolume::cacheMirrorBlock_ = 0; // mirror block for second FAT + +//------------------------------------------------------------------------------ +// find a contiguous group of clusters +uint8_t RP2040_SdVolume::allocContiguous(uint32_t count, uint32_t* curCluster) +{ + // start of group + uint32_t bgnCluster; + + // flag to save place to start next search + uint8_t setStart; + + // set search start cluster + if (*curCluster) + { + // try to make file contiguous + bgnCluster = *curCluster + 1; + + // don't save new start location + setStart = false; + } + else + { + // start at likely place for free cluster + bgnCluster = allocSearchStart_; + + // save next search start if one cluster + setStart = 1 == count; + } + + // end of group + uint32_t endCluster = bgnCluster; + + // last cluster of FAT + uint32_t fatEnd = clusterCount_ + 1; + + // search the FAT for free clusters + for (uint32_t n = 0;; n++, endCluster++) + { + // can't find space checked all clusters + if (n >= clusterCount_) + { + return false; + } + + // past end - start from beginning of FAT + if (endCluster > fatEnd) + { + bgnCluster = endCluster = 2; + } + + uint32_t f; + + if (!fatGet(endCluster, &f)) + { + return false; + } + + if (f != 0) + { + // cluster in use try next cluster as bgnCluster + bgnCluster = endCluster + 1; + } + else if ((endCluster - bgnCluster + 1) == count) + { + // done - found space + break; + } + } + + // mark end of chain + if (!fatPutEOC(endCluster)) + { + return false; + } + + // link clusters + while (endCluster > bgnCluster) + { + if (!fatPut(endCluster - 1, endCluster)) + { + return false; + } + + endCluster--; + } + + if (*curCluster != 0) + { + // connect chains + if (!fatPut(*curCluster, bgnCluster)) + { + return false; + } + } + + // return first cluster number to caller + *curCluster = bgnCluster; + + // remember possible next free cluster + if (setStart) + { + allocSearchStart_ = bgnCluster + 1; + } + + return true; +} +//------------------------------------------------------------------------------ +uint8_t RP2040_SdVolume::cacheFlush(uint8_t blocking) +{ + if (cacheDirty_) + { + if (!sdCard_->writeBlock(cacheBlockNumber_, cacheBuffer_.data, blocking)) + { + return false; + } + + if (!blocking) + { + return true; + } + + // mirror FAT tables + if (!cacheMirrorBlockFlush(blocking)) + { + return false; + } + + cacheDirty_ = 0; + } + + return true; +} +//------------------------------------------------------------------------------ +uint8_t RP2040_SdVolume::cacheMirrorBlockFlush(uint8_t blocking) +{ + if (cacheMirrorBlock_) + { + if (!sdCard_->writeBlock(cacheMirrorBlock_, cacheBuffer_.data, blocking)) + { + return false; + } + + cacheMirrorBlock_ = 0; + } + + return true; +} +//------------------------------------------------------------------------------ +uint8_t RP2040_SdVolume::cacheRawBlock(uint32_t blockNumber, uint8_t action) +{ + if (cacheBlockNumber_ != blockNumber) + { + if (!cacheFlush()) + { + return false; + } + + if (!sdCard_->readBlock(blockNumber, cacheBuffer_.data)) + { + return false; + } + + cacheBlockNumber_ = blockNumber; + } + + cacheDirty_ |= action; + + return true; +} +//------------------------------------------------------------------------------ +// cache a zero block for blockNumber +uint8_t RP2040_SdVolume::cacheZeroBlock(uint32_t blockNumber) +{ + if (!cacheFlush()) + { + return false; + } + + // loop take less flash than memset(cacheBuffer_.data, 0, 512); + for (uint16_t i = 0; i < 512; i++) + { + cacheBuffer_.data[i] = 0; + } + + cacheBlockNumber_ = blockNumber; + cacheSetDirty(); + + return true; +} +//------------------------------------------------------------------------------ +// return the size in bytes of a cluster chain +uint8_t RP2040_SdVolume::chainSize(uint32_t cluster, uint32_t* size) const +{ + uint32_t s = 0; + + do + { + if (!fatGet(cluster, &cluster)) + { + return false; + } + + s += 512UL << clusterSizeShift_; + } while (!isEOC(cluster)); + + *size = s; + return true; +} + +//------------------------------------------------------------------------------ +// Fetch a FAT entry +uint8_t RP2040_SdVolume::fatGet(uint32_t cluster, uint32_t* value) const +{ + if (cluster > (clusterCount_ + 1)) + { + return false; + } + + uint32_t lba = fatStartBlock_; + lba += fatType_ == 16 ? cluster >> 8 : cluster >> 7; + + if (lba != cacheBlockNumber_) + { + if (!cacheRawBlock(lba, CACHE_FOR_READ)) + { + return false; + } + } + + if (fatType_ == 16) + { + *value = cacheBuffer_.fat16[cluster & 0XFF]; + } + else + { + *value = cacheBuffer_.fat32[cluster & 0X7F] & FAT32MASK; + } + + return true; +} + +//------------------------------------------------------------------------------ +// Store a FAT entry +uint8_t RP2040_SdVolume::fatPut(uint32_t cluster, uint32_t value) +{ + // error if reserved cluster + if (cluster < 2) + { + return false; + } + + // error if not in FAT + if (cluster > (clusterCount_ + 1)) + { + return false; + } + + // calculate block address for entry + uint32_t lba = fatStartBlock_; + lba += fatType_ == 16 ? cluster >> 8 : cluster >> 7; + + if (lba != cacheBlockNumber_) + { + if (!cacheRawBlock(lba, CACHE_FOR_READ)) + { + return false; + } + } + + // store entry + if (fatType_ == 16) + { + cacheBuffer_.fat16[cluster & 0XFF] = value; + } + else + { + cacheBuffer_.fat32[cluster & 0X7F] = value; + } + + cacheSetDirty(); + + // mirror second FAT + if (fatCount_ > 1) + { + cacheMirrorBlock_ = lba + blocksPerFat_; + } + + return true; +} +//------------------------------------------------------------------------------ +// free a cluster chain +uint8_t RP2040_SdVolume::freeChain(uint32_t cluster) +{ + // clear free cluster location + allocSearchStart_ = 2; + + do + { + uint32_t next; + + if (!fatGet(cluster, &next)) + { + return false; + } + + // free cluster + if (!fatPut(cluster, 0)) + { + return false; + } + + cluster = next; + } while (!isEOC(cluster)); + + return true; +} +//------------------------------------------------------------------------------ +/** + Initialize a FAT volume. + + \param[in] dev The SD card where the volume is located. + + \param[in] part The partition to be used. Legal values for \a part are + 1-4 to use the corresponding partition on a device formatted with + a MBR, Master Boot Record, or zero if the device is formatted as + a super floppy with the FAT boot sector in block zero. + + \return The value one, true, is returned for success and + the value zero, false, is returned for failure. Reasons for + failure include not finding a valid partition, not finding a valid + FAT file system in the specified partition or an I/O error. +*/ +uint8_t RP2040_SdVolume::init(Sd2Card* dev, uint8_t part) +{ + uint32_t volumeStartBlock = 0; + sdCard_ = dev; + + // if part == 0 assume super floppy with FAT boot sector in block zero + // if part > 0 assume mbr volume with partition table + if (part) + { + if (part > 4) + { + return false; + } + + if (!cacheRawBlock(volumeStartBlock, CACHE_FOR_READ)) + { + return false; + } + + part_t* p = &cacheBuffer_.mbr.part[part - 1]; + + if ((p->boot & 0X7F) != 0 || p->totalSectors < 100 || p->firstSector == 0) + { + // not a valid partition + return false; + } + + volumeStartBlock = p->firstSector; + } + + if (!cacheRawBlock(volumeStartBlock, CACHE_FOR_READ)) + { + return false; + } + + bpb_t* bpb = &cacheBuffer_.fbs.bpb; + + if (bpb->bytesPerSector != 512 || bpb->fatCount == 0 || bpb->reservedSectorCount == 0 || bpb->sectorsPerCluster == 0) + { + // not valid FAT volume + return false; + } + + fatCount_ = bpb->fatCount; + blocksPerCluster_ = bpb->sectorsPerCluster; + + // determine shift that is same as multiply by blocksPerCluster_ + clusterSizeShift_ = 0; + + while (blocksPerCluster_ != (1 << clusterSizeShift_)) + { + // error if not power of 2 + if (clusterSizeShift_++ > 7) + { + return false; + } + } + + blocksPerFat_ = bpb->sectorsPerFat16 ? bpb->sectorsPerFat16 : bpb->sectorsPerFat32; + + fatStartBlock_ = volumeStartBlock + bpb->reservedSectorCount; + + // count for FAT16 zero for FAT32 + rootDirEntryCount_ = bpb->rootDirEntryCount; + + // directory start for FAT16 dataStart for FAT32 + rootDirStart_ = fatStartBlock_ + bpb->fatCount * blocksPerFat_; + + // data start for FAT16 and FAT32 + dataStartBlock_ = rootDirStart_ + ((32 * bpb->rootDirEntryCount + 511) / 512); + + // total blocks for FAT16 or FAT32 + uint32_t totalBlocks = bpb->totalSectors16 ? + bpb->totalSectors16 : bpb->totalSectors32; + // total data blocks + clusterCount_ = totalBlocks - (dataStartBlock_ - volumeStartBlock); + + // divide by cluster size to get cluster count + clusterCount_ >>= clusterSizeShift_; + + // FAT type is determined by cluster count + if (clusterCount_ < 4085) + { + fatType_ = 12; + } + else if (clusterCount_ < 65525) + { + fatType_ = 16; + } + else + { + rootDirStart_ = bpb->fat32RootCluster; + fatType_ = 32; + } + + return true; +}