| Support has been added for the Arduino Due. | |||||
| You must connect your SD socket to the 6-pin "ISP connector". You must have short | |||||
| wires or a custom shield to run at full speed, 42 MHz. | |||||
| If you have problems use a lower SPI speed. You can also check for SPI | |||||
| errors by editing SdFatCobfig.h to enable CRC checking. | |||||
| You should be be able to use any digital pin for SD chip select. The default | |||||
| pin is SS which is pin 10 for Due. | |||||
| The default SPI rate is 42 MHz. You can set SD chip select and the SPI rate | |||||
| by calling: | |||||
| bool SdFat::begin(uint8_t chipSelectPin, uint8_t spiRateID); | |||||
| The second argument, spiRateID, sets the SCK rate and can be these symbols: | |||||
| SPI_FULL_SPEED - 42 MHz | |||||
| SPI_DIV3_SPEED - 28 MHz | |||||
| SPI_HALF_SPEED - 21 MHz | |||||
| SPI_DIV6_SPEED - 14 MHz | |||||
| SPI_QUARTER_SPEED 10.5 MHz | |||||
| SPI_EIGHTH_SPEED 5.25 MHz | |||||
| Large reads and writes use fast multi-block SD read/write commands. For optimal | |||||
| speed, use records that are a multiple of 512 bytes. | |||||
| Run the bench.ino example to explore large read/write speed. | |||||
| Replace this line: | |||||
| #define BUF_SIZE 100 | |||||
| With a large size like this: | |||||
| #define BUF_SIZE 8192 | |||||
| For best results the record size should be a power of two (512, 1024, 2048, | |||||
| 4096, 8192). In this case records will be aligned with FAT cluster boundaries. | |||||
| Since Due is fast, increase the test file size by editing this line: | |||||
| #define FILE_SIZE_MB 5 | |||||
| Run the PrintBenchmark.ino example to compare text formatting speed of Due | |||||
| with AVR boards. | |||||
| A number of options are available to configure SPI for the Due board. | |||||
| You can use the standard SPI.h library by editing SdFatConfig.h and set | |||||
| USE_ARDUINO_SPI_LIBRARY nonzero. You must include SPI.h in your sketch. | |||||
| Several options can be set in Sd2Card.cpp in the USE_NATIVE_SAM3X_SPI | |||||
| section. These include USE_SAM3X_DMAC to control use of DMA and | |||||
| USE_SAM3X_BUS_MATRIX_FIX to change Bus Matrix operation. Most people | |||||
| will not need to change these. |
| SdFat has support for multiple SD cards. This requires multiple instances | |||||
| of SdFat objects. | |||||
| You must edit SdFatConfig.h to enable multiple instances of SdFat. Set | |||||
| USE_MULTIPLE_CARDS nonzero like this: | |||||
| #define USE_MULTIPLE_CARDS 1 | |||||
| Look at TwoCards.pde in the SdFat/examples folder. This example demonstrates | |||||
| use of two SD cards. | |||||
| Read WorkingDirectory.txt for more information on volume working | |||||
| directories and the current working directory. |
| For those who don't like too much documentation. | |||||
| To use this library place the SdFat folder into the libraries | |||||
| subfolder in your main sketches folder. You may need to | |||||
| create the libraries folder. Restart the Arduino IDE if | |||||
| it was open. | |||||
| Run the QuickStart.ino sketch from the | |||||
| libraries/SdFat/examples/QuickStart folder. Click the | |||||
| IDE up-arrow icon then -> libraries -> SdFat -> QuickStart. | |||||
| You can also click File -> Examples -> SdFat -> QuickStart. | |||||
| If problems occur try reading more documentation and use these | |||||
| forums for help: | |||||
| http://forums.adafruit.com/ | |||||
| http://arduino.cc/forum/ | |||||
| If QuickStart.ino runs successfully try more examples. |
| To enable support for SPI transactions, edit SfFatCinfig.h and modify these | |||||
| defines. | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * Set ENABLE_SPI_TRANSACTION nonzero to enable the SPI transaction feature | |||||
| * of the standard Arduino SPI library. You must include SPI.h in your | |||||
| * sketches when ENABLE_SPI_TRANSACTION is nonzero. | |||||
| */ | |||||
| #define ENABLE_SPI_TRANSACTION 0 | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * Set ENABLE_SPI_YIELD nonzero to enable release of the SPI bus during | |||||
| * SD card busy waits. | |||||
| * | |||||
| * This will allow interrupt routines to access the SPI bus if | |||||
| * ENABLE_SPI_TRANSACTION is nonzero. | |||||
| * | |||||
| * Setting ENABLE_SPI_YIELD will introduce some extra overhead and will | |||||
| * slightly slow transfer rates. A few older SD cards may fail when | |||||
| * ENABLE_SPI_YIELD is nonzero. | |||||
| */ | |||||
| #define ENABLE_SPI_YIELD 0 |
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| int MinimumSerial::read() { | int MinimumSerial::read() { | ||||
| if (UCSR0A & (1 << RXC0)) return UDR0; | |||||
| if (UCSR0A & (1 << RXC0)) { | |||||
| return UDR0; | |||||
| } | |||||
| return -1; | return -1; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ |
| /** | /** | ||||
| * Unbuffered read | * Unbuffered read | ||||
| * \return -1 if no character is available or an available character. | * \return -1 if no character is available or an available character. | ||||
| */ | |||||
| */ | |||||
| int read(); | int read(); | ||||
| /** | |||||
| * Unbuffered write | |||||
| * | |||||
| * \param[in] b byte to write. | |||||
| * \return 1 | |||||
| */ | |||||
| /** | |||||
| * Unbuffered write | |||||
| * | |||||
| * \param[in] b byte to write. | |||||
| * \return 1 | |||||
| */ | |||||
| size_t write(uint8_t b); | size_t write(uint8_t b); | ||||
| using Print::write; | using Print::write; | ||||
| }; | }; |
| #include "utility/FatLib.h" | #include "utility/FatLib.h" | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** SdFat version YYYYMMDD */ | /** SdFat version YYYYMMDD */ | ||||
| #define SD_FAT_VERSION 20141115 | |||||
| #define SD_FAT_VERSION 20141204 | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| * \class SdFatBase | * \class SdFatBase | ||||
| */ | */ | ||||
| bool begin(SdSpiCard::m_spi_t* spi, uint8_t csPin = SS, uint8_t divisor = 2) { | bool begin(SdSpiCard::m_spi_t* spi, uint8_t csPin = SS, uint8_t divisor = 2) { | ||||
| return m_sdCard.begin(spi, csPin, divisor) && | return m_sdCard.begin(spi, csPin, divisor) && | ||||
| FatFileSystem::begin(&m_vwd); | |||||
| FatFileSystem::begin(&m_vwd); | |||||
| } | } | ||||
| /** \return Pointer to SD card object */ | /** \return Pointer to SD card object */ | ||||
| SdSpiCard *card() {return &m_sdCard;} | |||||
| SdSpiCard *card() { | |||||
| return &m_sdCard; | |||||
| } | |||||
| /** %Print any SD error code to Serial and halt. */ | /** %Print any SD error code to Serial and halt. */ | ||||
| void errorHalt() {errorHalt(&Serial);} | |||||
| void errorHalt() { | |||||
| errorHalt(&Serial); | |||||
| } | |||||
| /** %Print any SD error code and halt. | /** %Print any SD error code and halt. | ||||
| * | * | ||||
| * \param[in] pr Print destination. | * \param[in] pr Print destination. | ||||
| * | * | ||||
| * \param[in] msg Message to print. | * \param[in] msg Message to print. | ||||
| */ | */ | ||||
| void errorHalt(char const* msg) {errorHalt(&Serial, msg);} | |||||
| void errorHalt(char const* msg) { | |||||
| errorHalt(&Serial, msg); | |||||
| } | |||||
| /** %Print msg, any SD error code, and halt. | /** %Print msg, any SD error code, and halt. | ||||
| * | * | ||||
| * \param[in] pr Print destination. | * \param[in] pr Print destination. | ||||
| * | * | ||||
| * \param[in] msg Message to print. | * \param[in] msg Message to print. | ||||
| */ | */ | ||||
| void errorHalt(const __FlashStringHelper* msg) {errorHalt(&Serial, msg);} | |||||
| void errorHalt(const __FlashStringHelper* msg) { | |||||
| errorHalt(&Serial, msg); | |||||
| } | |||||
| /** %Print msg, any SD error code, and halt. | /** %Print msg, any SD error code, and halt. | ||||
| * | * | ||||
| * \param[in] pr Print destination. | * \param[in] pr Print destination. | ||||
| */ | */ | ||||
| void errorHalt(Print* pr, const __FlashStringHelper* msg); | void errorHalt(Print* pr, const __FlashStringHelper* msg); | ||||
| /** %Print any SD error code to Serial */ | /** %Print any SD error code to Serial */ | ||||
| void errorPrint() {errorPrint(&Serial);} | |||||
| void errorPrint() { | |||||
| errorPrint(&Serial); | |||||
| } | |||||
| /** %Print any SD error code. | /** %Print any SD error code. | ||||
| * \param[in] pr Print device. | * \param[in] pr Print device. | ||||
| */ | */ | ||||
| * | * | ||||
| * \param[in] msg Message to print. | * \param[in] msg Message to print. | ||||
| */ | */ | ||||
| void errorPrint(const char* msg) {errorPrint(&Serial, msg);} | |||||
| void errorPrint(const char* msg) { | |||||
| errorPrint(&Serial, msg); | |||||
| } | |||||
| /** %Print msg, any SD error code. | /** %Print msg, any SD error code. | ||||
| * | * | ||||
| * \param[in] pr Print destination. | * \param[in] pr Print destination. | ||||
| * | * | ||||
| * \param[in] msg Message to print. | * \param[in] msg Message to print. | ||||
| */ | */ | ||||
| void errorPrint(const __FlashStringHelper* msg) {errorPrint(&Serial, msg);} | |||||
| void errorPrint(const __FlashStringHelper* msg) { | |||||
| errorPrint(&Serial, msg); | |||||
| } | |||||
| /** %Print msg, any SD error code. | /** %Print msg, any SD error code. | ||||
| * | * | ||||
| * \param[in] pr Print destination. | * \param[in] pr Print destination. | ||||
| * \param[in] msg Message to print. | * \param[in] msg Message to print. | ||||
| */ | */ | ||||
| void errorPrint(Print* pr, const __FlashStringHelper* msg); | void errorPrint(Print* pr, const __FlashStringHelper* msg); | ||||
| /** Diagnostic call to initialize FatFileSystem. | |||||
| /** Diagnostic call to initialize FatFileSystem - use for | |||||
| * diagnostic purposes only. | |||||
| * \return true for success else false. | * \return true for success else false. | ||||
| */ | */ | ||||
| bool fsBegin() {return FatFileSystem::begin(&m_vwd);} | |||||
| bool fsBegin() { | |||||
| return FatFileSystem::begin(&m_vwd); | |||||
| } | |||||
| /** %Print any SD error code and halt. */ | /** %Print any SD error code and halt. */ | ||||
| void initErrorHalt() {initErrorHalt(&Serial);} | |||||
| void initErrorHalt() { | |||||
| initErrorHalt(&Serial); | |||||
| } | |||||
| /** %Print error details and halt after begin fails. | /** %Print error details and halt after begin fails. | ||||
| * | * | ||||
| * \param[in] pr Print destination. | * \param[in] pr Print destination. | ||||
| * | * | ||||
| * \param[in] msg Message to print. | * \param[in] msg Message to print. | ||||
| */ | */ | ||||
| void initErrorHalt(char const *msg) {initErrorHalt(&Serial, msg);} | |||||
| void initErrorHalt(char const *msg) { | |||||
| initErrorHalt(&Serial, msg); | |||||
| } | |||||
| /**Print message, error details, and halt after SdFatBase::init() fails. | /**Print message, error details, and halt after SdFatBase::init() fails. | ||||
| * \param[in] pr Print device. | * \param[in] pr Print device. | ||||
| * \param[in] msg Message to print. | * \param[in] msg Message to print. | ||||
| */ | */ | ||||
| void initErrorHalt(Print* pr, char const *msg); | void initErrorHalt(Print* pr, char const *msg); | ||||
| /**Print message, error details, and halt after SdFat::init() fails. | |||||
| * | |||||
| * \param[in] msg Message to print. | |||||
| */ | |||||
| /**Print message, error details, and halt after SdFat::init() fails. | |||||
| * | |||||
| * \param[in] msg Message to print. | |||||
| */ | |||||
| void initErrorHalt(const __FlashStringHelper* msg) { | void initErrorHalt(const __FlashStringHelper* msg) { | ||||
| initErrorHalt(&Serial, msg); | initErrorHalt(&Serial, msg); | ||||
| } | } | ||||
| /**Print message, error details, and halt after SdFatBase::init() fails. | |||||
| * \param[in] pr Print device for message. | |||||
| * \param[in] msg Message to print. | |||||
| */ | |||||
| void initErrorHalt(Print* pr, const __FlashStringHelper* msg); | void initErrorHalt(Print* pr, const __FlashStringHelper* msg); | ||||
| /** Print error details after SdFat::init() fails. */ | /** Print error details after SdFat::init() fails. */ | ||||
| void initErrorPrint() {initErrorPrint(&Serial);} | |||||
| void initErrorPrint() { | |||||
| initErrorPrint(&Serial); | |||||
| } | |||||
| /** Print error details after SdFatBase::init() fails. | /** Print error details after SdFatBase::init() fails. | ||||
| * | * | ||||
| * \param[in] pr Print destination. | * \param[in] pr Print destination. | ||||
| * | * | ||||
| * \param[in] msg Message to print. | * \param[in] msg Message to print. | ||||
| */ | */ | ||||
| void initErrorPrint(char const *msg) {initErrorPrint(&Serial, msg);} | |||||
| void initErrorPrint(char const *msg) { | |||||
| initErrorPrint(&Serial, msg); | |||||
| } | |||||
| /**Print message and error details and halt after SdFatBase::init() fails. | /**Print message and error details and halt after SdFatBase::init() fails. | ||||
| * | * | ||||
| * \param[in] pr Print destination. | * \param[in] pr Print destination. | ||||
| */ | */ | ||||
| File open(const char *path, uint8_t mode = FILE_READ); | File open(const char *path, uint8_t mode = FILE_READ); | ||||
| /** \return a pointer to the volume working directory. */ | /** \return a pointer to the volume working directory. */ | ||||
| SdBaseFile* vwd() {return &m_vwd;} | |||||
| SdBaseFile* vwd() { | |||||
| return &m_vwd; | |||||
| } | |||||
| using FatFileSystem::ls; | using FatFileSystem::ls; | ||||
| private: | private: | ||||
| uint8_t cardErrorCode() {return m_sdCard.errorCode();} | |||||
| uint8_t cardErrorData() {return m_sdCard.errorData();} | |||||
| uint8_t cardErrorCode() { | |||||
| return m_sdCard.errorCode(); | |||||
| } | |||||
| uint8_t cardErrorData() { | |||||
| return m_sdCard.errorData(); | |||||
| } | |||||
| bool readBlock(uint32_t block, uint8_t* dst) { | bool readBlock(uint32_t block, uint8_t* dst) { | ||||
| return m_sdCard.readBlock(block, dst); | return m_sdCard.readBlock(block, dst); | ||||
| } | } | ||||
| bool begin(uint8_t csPin = SS, uint8_t divisor = 2) { | bool begin(uint8_t csPin = SS, uint8_t divisor = 2) { | ||||
| return SdFatBase::begin(&m_spi, csPin, divisor); | return SdFatBase::begin(&m_spi, csPin, divisor); | ||||
| } | } | ||||
| /** Initialize SD card - use for diagnostic purposes. | |||||
| /** Diagnostic call to initialize SD card - use for diagnostic purposes only. | |||||
| * \param[in] csPin SD card chip select pin. | * \param[in] csPin SD card chip select pin. | ||||
| * \param[in] divisor SPI divisor. | * \param[in] divisor SPI divisor. | ||||
| * \return true for success else false. | * \return true for success else false. | ||||
| SpiDefault_t m_spi; | SpiDefault_t m_spi; | ||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| #if USE_MULTIPLE_SPI_TYPES || defined(DOXYGEN) | |||||
| #if SD_SPI_CONFIGURATION >= 3 || defined(DOXYGEN) | |||||
| /** | /** | ||||
| * \class SdFatLibSpi | * \class SdFatLibSpi | ||||
| * \brief SdFat class using the standard Arduino SPI library. | * \brief SdFat class using the standard Arduino SPI library. | ||||
| */ | */ | ||||
| class SdFatLibSpi: public SdFatBase { | class SdFatLibSpi: public SdFatBase { | ||||
| public: | public: | ||||
| /** Initialize SD card and file system. | |||||
| * | |||||
| * \param[in] csPin SD card chip select pin. | |||||
| * \param[in] divisor SPI divisor. | |||||
| * \return true for success else false. | |||||
| */ | |||||
| /** Initialize SD card and file system. | |||||
| * | |||||
| * \param[in] csPin SD card chip select pin. | |||||
| * \param[in] divisor SPI divisor. | |||||
| * \return true for success else false. | |||||
| */ | |||||
| bool begin(uint8_t csPin = SS, uint8_t divisor = 2) { | bool begin(uint8_t csPin = SS, uint8_t divisor = 2) { | ||||
| return SdFatBase::begin(&m_spi, csPin, divisor); | return SdFatBase::begin(&m_spi, csPin, divisor); | ||||
| } | } | ||||
| /** Initialize SD card - use for diagnostic purposes. | |||||
| /** Diagnostic call to initialize SD card - use for diagnostic purposes only. | |||||
| * \param[in] csPin SD card chip select pin. | * \param[in] csPin SD card chip select pin. | ||||
| * \param[in] divisor SPI divisor. | * \param[in] divisor SPI divisor. | ||||
| * \return true for success else false. | * \return true for success else false. | ||||
| bool begin(uint8_t csPin = SS, uint8_t divisor = 2) { | bool begin(uint8_t csPin = SS, uint8_t divisor = 2) { | ||||
| return SdFatBase::begin(&m_spi, csPin, divisor); | return SdFatBase::begin(&m_spi, csPin, divisor); | ||||
| } | } | ||||
| /** Initialize SD card - use for diagnostic purposes. | |||||
| /** Diagnostic call to initialize SD card - use for diagnostic purposes only. | |||||
| * \param[in] csPin SD card chip select pin. | * \param[in] csPin SD card chip select pin. | ||||
| * \param[in] divisor SPI divisor. | * \param[in] divisor SPI divisor. | ||||
| * \return true for success else false. | * \return true for success else false. | ||||
| private: | private: | ||||
| SdSpiSoft<MisoPin, MosiPin, SckPin> m_spi; | SdSpiSoft<MisoPin, MosiPin, SckPin> m_spi; | ||||
| }; | }; | ||||
| #endif // USE_MULTIPLE_SPI_TYPES | |||||
| #endif /// SD_SPI_CONFIGURATION >= 3 || defined(DOXYGEN) | |||||
| #endif // SdFat_h | #endif // SdFat_h |
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void SdFatBase::errorPrint(Print* pr) { | void SdFatBase::errorPrint(Print* pr) { | ||||
| if (!cardErrorCode()) return; | |||||
| if (!cardErrorCode()) { | |||||
| return; | |||||
| } | |||||
| pr->print(F("SD errorCode: 0X")); | pr->print(F("SD errorCode: 0X")); | ||||
| pr->print(cardErrorCode(), HEX); | pr->print(cardErrorCode(), HEX); | ||||
| pr->print(F(",0X")); | pr->print(F(",0X")); | ||||
| initErrorHalt(pr); | initErrorHalt(pr); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /**Print message, error details, and halt after SdFatBase::init() fails. | |||||
| * \param[in] pr Print device for message. | |||||
| * \param[in] msg Message to print. | |||||
| */ | |||||
| void SdFatBase::initErrorHalt(Print* pr, const __FlashStringHelper* msg) { | void SdFatBase::initErrorHalt(Print* pr, const __FlashStringHelper* msg) { | ||||
| pr->println(msg); | pr->println(msg); | ||||
| initErrorHalt(pr); | initErrorHalt(pr); |
| #endif // __AVR__ | #endif // __AVR__ | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** | /** | ||||
| * Set USE_MULTIPLE_SPI_TYPES nonzero to enable the SdFatSoftSpi and | |||||
| * SdFatLibSpi classes. SdFatSoftSpi uses software SPI and SdFatLibSpi | |||||
| * uses the standard Arduino SPI library. | |||||
| * Set USE_LONG_FILE_NAMES nonzero to use long file names (LFN). | |||||
| * Long File Name are limited to a maximum length of 255 characters. | |||||
| * | |||||
| * This implementation allows 7-bit characters in the range | |||||
| * 0X20 to 0X7E except the following characters are not allowed: | |||||
| * | |||||
| * < (less than) | |||||
| * > (greater than) | |||||
| * : (colon) | |||||
| * " (double quote) | |||||
| * / (forward slash) | |||||
| * \ (backslash) | |||||
| * | (vertical bar or pipe) | |||||
| * ? (question mark) | |||||
| * * (asterisk) | |||||
| * | |||||
| */ | */ | ||||
| #define USE_MULTIPLE_SPI_TYPES 0 | |||||
| #define USE_LONG_FILE_NAMES 1 | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * The symbol SD_SPI_CONFIGURATION defines SPI access to the SD card. | |||||
| * | |||||
| * IF SD_SPI_CONFIGUTATION is define to be zero, only the SdFat class | |||||
| * is define and SdFat uses a fast custom SPI implementation. | |||||
| * | |||||
| * If SD_SPI_CONFIGURATION is define to be one, only the SdFat class is | |||||
| * define and SdFat uses the standard Arduino SD.h library. | |||||
| * | |||||
| * If SD_SPI_CONFIGURATION is define to be two, only the SdFat class is | |||||
| * define and SdFat uses software SPI on the pins defined below. | |||||
| * | |||||
| * If SD_SPI_CONFIGURATION is define to be three, the three classes, SdFat, | |||||
| * SdFatLibSpi, and SdFatSoftSpi are defined. SdFat uses the fast | |||||
| * custom SPI implementation. SdFatLibSpi uses the standard Arduino SPI | |||||
| * library. SdFatSoftSpi is a template class that uses Software SPI. The | |||||
| * template parameters define the software SPI pins. See the ThreeCard | |||||
| * example for simultaneous use of all three classes. | |||||
| */ | |||||
| #define SD_SPI_CONFIGURATION 0 | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * If SD_SPI_CONFIGURATION is defined to be two, these definitions | |||||
| * will define the pins used for software SPI. | |||||
| * | |||||
| * The default definition allows Uno shields to be used on other boards. | |||||
| */ | |||||
| /** Software SPI Master Out Slave In pin */ | |||||
| uint8_t const SOFT_SPI_MOSI_PIN = 11; | |||||
| /** Software SPI Master In Slave Out pin */ | |||||
| uint8_t const SOFT_SPI_MISO_PIN = 12; | |||||
| /** Software SPI Clock pin */ | |||||
| uint8_t const SOFT_SPI_SCK_PIN = 13; | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** | /** | ||||
| * To enable SD card CRC checking set USE_SD_CRC nonzero. | * To enable SD card CRC checking set USE_SD_CRC nonzero. | ||||
| /** | /** | ||||
| * Set ENABLE_SPI_TRANSACTION nonzero to enable the SPI transaction feature | * Set ENABLE_SPI_TRANSACTION nonzero to enable the SPI transaction feature | ||||
| * of the standard Arduino SPI library. You must include SPI.h in your | * of the standard Arduino SPI library. You must include SPI.h in your | ||||
| * sketches when ENABLE_SPI_TRANSACTION is nonzero. | |||||
| * programs when ENABLE_SPI_TRANSACTION is nonzero. | |||||
| */ | */ | ||||
| #define ENABLE_SPI_TRANSACTION 0 | #define ENABLE_SPI_TRANSACTION 0 | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** | /** | ||||
| * Set ENABLE_SPI_YIELD nonzero to enable release of the SPI bus during | * Set ENABLE_SPI_YIELD nonzero to enable release of the SPI bus during | ||||
| * SD card busy waits. | |||||
| * SD card busy waits. | |||||
| * | * | ||||
| * This will allow interrupt routines to access the SPI bus if | |||||
| * This will allow interrupt routines to access the SPI bus if | |||||
| * ENABLE_SPI_TRANSACTION is nonzero. | * ENABLE_SPI_TRANSACTION is nonzero. | ||||
| * | |||||
| * | |||||
| * Setting ENABLE_SPI_YIELD will introduce some extra overhead and will | * Setting ENABLE_SPI_YIELD will introduce some extra overhead and will | ||||
| * slightly slow transfer rates. A few older SD cards may fail when | |||||
| * slightly slow transfer rates. A few older SD cards may fail when | |||||
| * ENABLE_SPI_YIELD is nonzero. | * ENABLE_SPI_YIELD is nonzero. | ||||
| */ | */ | ||||
| #define ENABLE_SPI_YIELD 0 | #define ENABLE_SPI_YIELD 0 | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** | |||||
| * Set USE_ARDUINO_SPI_LIBRARY nonzero to force use of the Arduino Standard | |||||
| * SPI library in the SdFat class. This will override native and software | |||||
| * SPI for all boards. | |||||
| */ | |||||
| #define USE_ARDUINO_SPI_LIBRARY 0 | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * Set AVR_SOFT_SPI nonzero to use software SPI in the SdFat class | |||||
| * on all AVR Arduinos. Set the soft SPI pins below. | |||||
| */ | |||||
| #define AVR_SOFT_SPI 0 | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * Set DUE_SOFT_SPI nonzero to use software SPI in the SdFat class | |||||
| * on Due Arduinos. Set the soft SPI pins below. | |||||
| */ | |||||
| #define DUE_SOFT_SPI 0 | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * Set LEONARDO_SOFT_SPI nonzero to use software SPI in the SdFat class | |||||
| * on Leonardo Arduinos. Set the soft SPI pins below. | |||||
| * | |||||
| * LEONARDO_SOFT_SPI allows an unmodified 328 Shield to be used | |||||
| * on Leonardo Arduinos. | |||||
| */ | |||||
| #define LEONARDO_SOFT_SPI 0 | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * Set MEGA_SOFT_SPI nonzero to use software SPI in the SdFat class | |||||
| * on Mega Arduinos. Set the soft SPI pins below. | |||||
| * | |||||
| * MEGA_SOFT_SPI allows an unmodified 328 Shield to be used | |||||
| * on Mega Arduinos. Set the soft SPI pins below. | |||||
| */ | |||||
| #define MEGA_SOFT_SPI 0 | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * Set TEENSY3_SOFT_SPI nonzero to use software SPI in the SdFat class | |||||
| * on Teensy 3.x boards. Set the soft SPI pins below. | |||||
| */ | |||||
| #define TEENSY3_SOFT_SPI 0 | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * Define software SPI pins. Default allows Uno shields to be used on other | |||||
| * boards. | |||||
| */ | |||||
| // define software SPI pins | |||||
| /** Software SPI Master Out Slave In pin */ | |||||
| uint8_t const SOFT_SPI_MOSI_PIN = 11; | |||||
| /** Software SPI Master In Slave Out pin */ | |||||
| uint8_t const SOFT_SPI_MISO_PIN = 12; | |||||
| /** Software SPI Clock pin */ | |||||
| uint8_t const SOFT_SPI_SCK_PIN = 13; | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | /** | ||||
| * Set FAT12_SUPPORT nonzero to enable use if FAT12 volumes. | * Set FAT12_SUPPORT nonzero to enable use if FAT12 volumes. | ||||
| * FAT12 has not been well tested and requires additional flash. | * FAT12 has not been well tested and requires additional flash. | ||||
| /** | /** | ||||
| * Set SD_FILE_USES_STREAM nonzero to use Stream instead of Print for SdFile. | * Set SD_FILE_USES_STREAM nonzero to use Stream instead of Print for SdFile. | ||||
| * Using Stream will use more flash and may cause compatibility problems | * Using Stream will use more flash and may cause compatibility problems | ||||
| * with code written for older versions of SdFat. | |||||
| * with code written for older versions of SdFat. | |||||
| */ | */ | ||||
| #define SD_FILE_USES_STREAM 0 | #define SD_FILE_USES_STREAM 0 | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** | /** | ||||
| * Set USE_SEPARATE_FAT_CACHE nonzero to use a second 512 byte cache | * Set USE_SEPARATE_FAT_CACHE nonzero to use a second 512 byte cache | ||||
| * for FAT table entries. Improves performance for large writes that | |||||
| * are not a multiple of 512 bytes. | |||||
| * for FAT table entries. This improves performance for large writes | |||||
| * that are not a multiple of 512 bytes. | |||||
| */ | */ | ||||
| #ifdef __arm__ | #ifdef __arm__ | ||||
| #define USE_SEPARATE_FAT_CACHE 1 | #define USE_SEPARATE_FAT_CACHE 1 |
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| #ifdef __arm__ | #ifdef __arm__ | ||||
| extern "C" char* sbrk(int incr); | extern "C" char* sbrk(int incr); | ||||
| /** Amount of free RAM | |||||
| * \return The number of free bytes. | |||||
| */ | |||||
| int SdFatUtil::FreeRam() { | int SdFatUtil::FreeRam() { | ||||
| char top; | char top; | ||||
| return &top - reinterpret_cast<char*>(sbrk(0)); | return &top - reinterpret_cast<char*>(sbrk(0)); | ||||
| } | } | ||||
| #endif // __arm | #endif // __arm | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** %Print a string in flash memory. | |||||
| * | |||||
| * \param[in] pr Print object for output. | |||||
| * \param[in] str Pointer to string stored in flash memory. | |||||
| */ | |||||
| void SdFatUtil::print_P(Print* pr, PGM_P str) { | void SdFatUtil::print_P(Print* pr, PGM_P str) { | ||||
| for (uint8_t c; (c = pgm_read_byte(str)); str++) pr->write(c); | |||||
| for (uint8_t c; (c = pgm_read_byte(str)); str++) { | |||||
| pr->write(c); | |||||
| } | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** %Print a string in flash memory followed by a CR/LF. | |||||
| * | |||||
| * \param[in] pr Print object for output. | |||||
| * \param[in] str Pointer to string stored in flash memory. | |||||
| */ | |||||
| void SdFatUtil::println_P(Print* pr, PGM_P str) { | void SdFatUtil::println_P(Print* pr, PGM_P str) { | ||||
| print_P(pr, str); | print_P(pr, str); | ||||
| pr->println(); | pr->println(); |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| * GNU General Public License for more details. | * GNU General Public License for more details. | ||||
| * | |||||
| * You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | ||||
| * along with the Arduino SdFat Library. If not, see | * along with the Arduino SdFat Library. If not, see | ||||
| * <http://www.gnu.org/licenses/>. | * <http://www.gnu.org/licenses/>. | ||||
| #define PgmPrintln(x) SerialPrintln_P(PSTR(x)) | #define PgmPrintln(x) SerialPrintln_P(PSTR(x)) | ||||
| namespace SdFatUtil { | namespace SdFatUtil { | ||||
| /** Amount of free RAM | |||||
| * \return The number of free bytes. | |||||
| */ | |||||
| int FreeRam(); | int FreeRam(); | ||||
| /** %Print a string in flash memory. | |||||
| * | |||||
| * \param[in] pr Print object for output. | |||||
| * \param[in] str Pointer to string stored in flash memory. | |||||
| */ | |||||
| void print_P(Print* pr, PGM_P str); | void print_P(Print* pr, PGM_P str); | ||||
| /** %Print a string in flash memory followed by a CR/LF. | |||||
| * | |||||
| * \param[in] pr Print object for output. | |||||
| * \param[in] str Pointer to string stored in flash memory. | |||||
| */ | |||||
| void println_P(Print* pr, PGM_P str); | void println_P(Print* pr, PGM_P str); | ||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** %Print a string in flash memory to Serial. | /** %Print a string in flash memory to Serial. | ||||
| inline void SerialPrintln_P(PGM_P str) { | inline void SerialPrintln_P(PGM_P str) { | ||||
| println_P(&Serial, str); | println_P(&Serial, str); | ||||
| } | } | ||||
| void SerialPrint_P(PGM_P str); | |||||
| void SerialPrintln_P(PGM_P str); | |||||
| } // namespace SdFatUtil | } // namespace SdFatUtil | ||||
| using namespace SdFatUtil; // NOLINT | using namespace SdFatUtil; // NOLINT | ||||
| #endif // #define SdFatUtil_h | #endif // #define SdFatUtil_h |
| /* Arduino SdFat Library | /* Arduino SdFat Library | ||||
| * Copyright (C) 2012 by William Greiman | * Copyright (C) 2012 by William Greiman | ||||
| * | |||||
| * | |||||
| * This file is part of the Arduino SdFat Library | * This file is part of the Arduino SdFat Library | ||||
| * | |||||
| * 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 | |||||
| * | |||||
| * 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 | * the Free Software Foundation, either version 3 of the License, or | ||||
| * (at your option) any later version. | * (at your option) any later version. | ||||
| * | |||||
| * | |||||
| * This Library is distributed in the hope that it will be useful, | * This Library is distributed in the hope that it will be useful, | ||||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| Experimental support for FAT12 can be enabled by setting FAT12_SUPPORT | Experimental support for FAT12 can be enabled by setting FAT12_SUPPORT | ||||
| nonzero in SdFatConfig.h. | nonzero in SdFatConfig.h. | ||||
| The %SdFat library only supports short 8.3 names. Limited Long %File Name | |||||
| access is provided by \ref SdFile::openNextLFN. See the LongFileName example. | |||||
| The %SdFat library supports Long %File Names or short 8.3 names. | |||||
| Edit the SdFatConfig.h file to select short or long file names. | |||||
| The main classes in %SdFat are SdFat, SdFatSoftSpi, SdFatLibSpi, | The main classes in %SdFat are SdFat, SdFatSoftSpi, SdFatLibSpi, | ||||
| SdBaseFile, SdFile, File, StdioStream, \ref fstream, \ref ifstream, | SdBaseFile, SdFile, File, StdioStream, \ref fstream, \ref ifstream, | ||||
| a current working directory, and simplifies initialization of other classes. | a current working directory, and simplifies initialization of other classes. | ||||
| The SdFat class uses a fast custom hardware SPI implementation. The | The SdFat class uses a fast custom hardware SPI implementation. The | ||||
| SdFatLibSpi class uses the standard Arduino SPI library. The SdFatSoftSpi | SdFatLibSpi class uses the standard Arduino SPI library. The SdFatSoftSpi | ||||
| class uses software SPI. | |||||
| class uses software SPI. | |||||
| The SdBaseFile class provides basic file access functions such as open(), | The SdBaseFile class provides basic file access functions such as open(), | ||||
| binary read(), binary write(), close(), remove(), and sync(). SdBaseFile | binary read(), binary write(), close(), remove(), and sync(). SdBaseFile | ||||
| Print class functions. | Print class functions. | ||||
| The File class has all the SdBaseFile functions plus the functions in | The File class has all the SdBaseFile functions plus the functions in | ||||
| the Arduino SD.h File class. This provides compatibility with the | |||||
| the Arduino SD.h File class. This provides compatibility with the | |||||
| Arduino SD.h library. | Arduino SD.h library. | ||||
| The StdioStream class implements functions similar to Linux/Unix standard | The StdioStream class implements functions similar to Linux/Unix standard | ||||
| buffered input/output. | |||||
| buffered input/output. | |||||
| The \ref fstream class implements C++ iostreams for both reading and writing | The \ref fstream class implements C++ iostreams for both reading and writing | ||||
| text files. | text files. | ||||
| The \ref ofstream class implements C++ iostreams for writing text files. | The \ref ofstream class implements C++ iostreams for writing text files. | ||||
| The classes \ref ifstream, \ref ofstream, \ref istream, and \ref ostream | |||||
| The classes \ref ifstream, \ref ofstream, \ref istream, and \ref ostream | |||||
| follow the C++ \ref iostream standard when possible. | follow the C++ \ref iostream standard when possible. | ||||
| There are many tutorials and much documentation about using C++ iostreams | There are many tutorials and much documentation about using C++ iostreams | ||||
| \section Install Installation | \section Install Installation | ||||
| You must manually install SdFat by copying the SdFat folder from the download | You must manually install SdFat by copying the SdFat folder from the download | ||||
| package to the Arduino libraries folder in you sketch book. | |||||
| package to the Arduino libraries folder in you sketch folder. | |||||
| See the Manual installation section of this guide. | See the Manual installation section of this guide. | ||||
| \section SDconfig SdFat Configuration | \section SDconfig SdFat Configuration | ||||
| Several configuration options may be changed by editing the SdFatConfig.h | Several configuration options may be changed by editing the SdFatConfig.h | ||||
| file in the SdFat folder. | |||||
| file in the %SdFat folder. | |||||
| Set USE_MULTIPLE_SPI_TYPES nonzero to enable the SdFatSoftSpi and | |||||
| SdFatLibSpi classes. SdFatSoftSpi uses software SPI and SdFatLibSpi | |||||
| uses the standard Arduino SPI library. | |||||
| Set USE_LONG_FILE_NAMES nonzero to enable Long %File Names. By default, | |||||
| Long %File Names are enabled. For the leanest fastest library disable | |||||
| Long %File Names. Long %File names require extra flash but no extra RAM. | |||||
| Opening Long %File Names can be slower than opening Short %File Names. | |||||
| Data read and write performance is not changed by the type of %File Name. | |||||
| Set SD_SPI_CONFIGURATION to enable various SPI options. The SdFatSoftSpi | |||||
| and SdFatLibSpi classes can be enabled. SdFatLibSpi uses the standard | |||||
| Arduino SPI library and SdFatSoftSpi uses software SPI. | |||||
| To enable SD card CRC checking set USE_SD_CRC nonzero. | To enable SD card CRC checking set USE_SD_CRC nonzero. | ||||
| Set FAT12_SUPPORT nonzero to enable use of FAT12 volumes. | Set FAT12_SUPPORT nonzero to enable use of FAT12 volumes. | ||||
| FAT12 has not been well tested and requires additional flash. | FAT12 has not been well tested and requires additional flash. | ||||
| Set USE_ARDUINO_SPI_LIBRARY nonzero to force use of the Arduino Standard | |||||
| SPI library in the SdFat class. This will override native and software | |||||
| SPI for all boards. | |||||
| Use of software SPI can be enabled in the SdFat class for selected boards | |||||
| by setting the symbols AVR_SOFT_SPI, DUE_SOFT_SPI, LEONARDO_SOFT_SPI, | |||||
| MEGA_SOFT_SPI, and TEENSY3_SOFT_SPI. | |||||
| Set ENABLE_SPI_TRANSACTION nonzero to enable the SPI transaction feature | Set ENABLE_SPI_TRANSACTION nonzero to enable the SPI transaction feature | ||||
| of the standard Arduino SPI library. You must include SPI.h in your | of the standard Arduino SPI library. You must include SPI.h in your | ||||
| sketches when ENABLE_SPI_TRANSACTION is nonzero. | |||||
| programs when ENABLE_SPI_TRANSACTION is nonzero. | |||||
| Set ENABLE_SPI_YIELD nonzero to enable release of the SPI bus during | Set ENABLE_SPI_YIELD nonzero to enable release of the SPI bus during | ||||
| SD card busy waits. | |||||
| SD card busy waits. | |||||
| \section SDPath Paths and Working Directories | |||||
| Relative paths in SdFat are resolved in a manner similar to Windows. | |||||
| Each instance of SdFat has a current directory. In SdFat this directory | |||||
| is called the volume working directory, vwd. Initially this directory is | |||||
| the root directory for the volume. | |||||
| The volume working directory is changed by calling SdFat::chdir(path). | |||||
| The call sd.chdir("/2014") will change the volume working directory | |||||
| for sd to "/2014", assuming "/2014" exists. | |||||
| Relative paths for SdFat member functions are resolved by starting at | |||||
| the volume working directory. | |||||
| For example, the call sd.mkdir("April") will create the directory | |||||
| "/2014/April" assuming the volume working directory is "/2014". | |||||
| SdFat has a current working directory, cwd, that is used to resolve paths | |||||
| for file.open() calls. | |||||
| For a single SD card the current working directory is always the volume | |||||
| working directory for that card. | |||||
| For multiple SD cards the current working directory is set to the volume | |||||
| working directory of a card by calling the SdFat::chvol() member function. | |||||
| The chvol() call is like the Windows \<drive letter>: command. | |||||
| The call sd2.chvol() will set the current working directory to the volume | |||||
| working directory for sd2. | |||||
| If the volume working directory for sd2 is "/music" the call | |||||
| file.open("BigBand.wav", O_READ); | |||||
| will then open "/music/BigBand.wav" on sd2. | |||||
| The following functions are used to change or get current directories. | |||||
| See the html documentation for more information. | |||||
| @code | |||||
| bool SdFat::chdir(bool set_cwd = false); | |||||
| bool SdFat::chdir(const char* path, bool set_cwd = false); | |||||
| void SdFat::chvol(); | |||||
| SdBaseFile* SdFat::vwd(); | |||||
| static SdBaseFile* SdBaseFile::cwd(); | |||||
| @endcode | |||||
| \section SDcard SD\SDHC Cards | \section SDcard SD\SDHC Cards | ||||
| \section Hardware Hardware Configuration | \section Hardware Hardware Configuration | ||||
| %SdFat was developed using an | %SdFat was developed using an | ||||
| <A HREF = "http://www.adafruit.com/"> Adafruit Industries</A> | |||||
| <A HREF = "http://www.adafruit.com/"> Adafruit Industries</A> | |||||
| Data Logging Shield. | Data Logging Shield. | ||||
| The hardware interface to the SD card should not use a resistor based level | The hardware interface to the SD card should not use a resistor based level | ||||
| 74LCX245. | 74LCX245. | ||||
| If you are using a resistor based level shifter and are having problems try | If you are using a resistor based level shifter and are having problems try | ||||
| setting the SPI bus frequency to 4 MHz. This can be done by using | |||||
| setting the SPI bus frequency to 4 MHz. This can be done by using | |||||
| card.init(SPI_HALF_SPEED) to initialize the SD card. | card.init(SPI_HALF_SPEED) to initialize the SD card. | ||||
| A feature to use software SPI is available. Software SPI is slower | A feature to use software SPI is available. Software SPI is slower | ||||
| than hardware SPI but allows any digital pins to be used. See | than hardware SPI but allows any digital pins to be used. See | ||||
| SdFatConfig.h for software SPI definitions. | SdFatConfig.h for software SPI definitions. | ||||
| An many shields designed for an Uno can be use on an Arduino Mega | |||||
| by defining MEGA_SOFT_SPI in SdFatConfig.h. | |||||
| \section comment Bugs and Comments | \section comment Bugs and Comments | ||||
| If you wish to report bugs or have comments, send email to fat16lib@sbcglobal.net. | |||||
| If you wish to report bugs or have comments, send email to | |||||
| fat16lib@sbcglobal.net. If possible, include a simple program that illustrates | |||||
| the bug or problem. | |||||
| \section Trouble Troubleshooting | |||||
| The two example programs QuickStart, and SdInfo are useful for troubleshooting. | |||||
| A message like this from SdInfo with erorCode 0X1 indicates the SD card | |||||
| is not seen by SdFat. This is often caused by a wiring error and reformatting | |||||
| the card will not solve the problem. | |||||
| <PRE> | |||||
| cardBegin failed | |||||
| SD errorCode: 0X1 | |||||
| SD errorData: 0XFF | |||||
| </PRE> | |||||
| Here is a similar message from QuickStart: | |||||
| <PRE> | |||||
| SD initialization failed. | |||||
| Do not reformat the card! | |||||
| Is the card correctly inserted? | |||||
| Is chipSelect set to the correct value? | |||||
| Does another SPI device need to be disabled? | |||||
| Is there a wiring/soldering problem? | |||||
| errorCode: 0x1, errorData: 0xff | |||||
| </PRE> | |||||
| Here is a message from QuickStart that indicates a formatting problem: | |||||
| <PRE> | |||||
| Card successfully initialized. | |||||
| Can't find a valid FAT16/FAT32 partition. | |||||
| Try reformatting the card. For best results use | |||||
| the SdFormatter program in SdFat/examples or download | |||||
| and use SDFormatter from www.sdcard.org/downloads. | |||||
| </PRE> | |||||
| The best source of recent information and help is the Arduino forum. | |||||
| http://arduino.cc/forum/ | |||||
| Also search the Adafruit forum. | |||||
| http://forums.adafruit.com/ | |||||
| If you are using a Teensy try. | |||||
| http://forum.pjrc.com/forum.php | |||||
| \section SdFatClass SdFat Usage | \section SdFatClass SdFat Usage | ||||
| SdFat supports Long File Names. Long names in SdFat are limited to 7-bit | |||||
| ASCII characters in the range 0X20 - 0XFE The following are reserved characters: | |||||
| <ul> | |||||
| <li>< (less than) | |||||
| <li>> (greater than) | |||||
| <li>: (colon) | |||||
| <li>" (double quote) | |||||
| <li>/ (forward slash) | |||||
| <li>\ (backslash) | |||||
| <li>| (vertical bar or pipe) | |||||
| <li>? (question mark) | |||||
| <li>* (asterisk) | |||||
| </ul> | |||||
| %SdFat uses a slightly restricted form of short names. | %SdFat uses a slightly restricted form of short names. | ||||
| Short names are limited to 8 characters followed by an optional period (.) | Short names are limited to 8 characters followed by an optional period (.) | ||||
| and extension of up to 3 characters. The characters may be any combination | and extension of up to 3 characters. The characters may be any combination | ||||
| $ % ' - _ @ ~ ` ! ( ) { } ^ # & | $ % ' - _ @ ~ ` ! ( ) { } ^ # & | ||||
| Short names are always converted to upper case and their original case | Short names are always converted to upper case and their original case | ||||
| value is lost. | |||||
| value is lost. Files that have a base-name where all characters have the | |||||
| same case and an extension where all characters have the same case will | |||||
| display properly. Examples this type name are UPPER.low, lower.TXT, | |||||
| UPPER.TXT, and lower.txt. | |||||
| An application which writes to a file using print(), println() or | An application which writes to a file using print(), println() or | ||||
| \link SdFile::write write() \endlink must call \link SdFile::sync() sync() \endlink | |||||
| at the appropriate time to force data and directory information to be written | |||||
| to the SD Card. Data and directory information are also written to the SD card | |||||
| when \link SdFile::close() close() \endlink is called. | |||||
| \link SdFile::write write() \endlink must close the file or call | |||||
| \link SdFile::sync() sync() \endlink at the appropriate time to | |||||
| force data and directory information to be written to the SD Card. | |||||
| Applications must use care calling \link SdFile::sync() sync() \endlink | Applications must use care calling \link SdFile::sync() sync() \endlink | ||||
| since 2048 bytes of I/O is required to update file and | since 2048 bytes of I/O is required to update file and | ||||
| http://www.sdcard.org/downloads | http://www.sdcard.org/downloads | ||||
| A formatter sketch, SdFormatter.ino, is included in the | |||||
| %SdFat/examples/SdFormatter directory. This sketch attempts to | |||||
| A formatter program, SdFormatter.ino, is included in the | |||||
| %SdFat/examples/SdFormatter directory. This program attempts to | |||||
| emulate SD Association's SDFormatter. | emulate SD Association's SDFormatter. | ||||
| SDFormatter aligns flash erase boundaries with file | SDFormatter aligns flash erase boundaries with file | ||||
| formatting of small cards. | formatting of small cards. | ||||
| Do not format the SD card with an OS utility, OS utilities do not format SD | Do not format the SD card with an OS utility, OS utilities do not format SD | ||||
| cards in conformance with the SD standard. | |||||
| cards in conformance with the SD standard. | |||||
| You should use a freshly formatted SD card for best performance. FAT | You should use a freshly formatted SD card for best performance. FAT | ||||
| file systems become slower if many files have been created and deleted. | file systems become slower if many files have been created and deleted. | ||||
| See the html documentation for a list. | See the html documentation for a list. | ||||
| To access these examples from the Arduino development environment | To access these examples from the Arduino development environment | ||||
| go to: %File -> Examples -> %SdFat -> \<Sketch Name\> | |||||
| go to: %File -> Examples -> %SdFat -> \<program Name\> | |||||
| Compile, upload to your Arduino and click on Serial Monitor to run | Compile, upload to your Arduino and click on Serial Monitor to run | ||||
| the example. | the example. | ||||
| getline - Example of getline from section 27.7.1.3 of the C++ standard. | getline - Example of getline from section 27.7.1.3 of the C++ standard. | ||||
| LongFileName - Example use of openNextLFN and open by index. | |||||
| LongFileName - Example use of openNext, printName, and open by index. | |||||
| LowLatencyLogger - A modifiable data logger for higher data rates. | LowLatencyLogger - A modifiable data logger for higher data rates. | ||||
| PrintBenchmark - A simple benchmark for printing to a text file. | PrintBenchmark - A simple benchmark for printing to a text file. | ||||
| QuickStart - A sketch to quickly test your SD card and SD shield/module. | |||||
| QuickStart - A program to quickly test your SD card and SD shield/module. | |||||
| RawWrite - A test of raw write functions for contiguous files. | RawWrite - A test of raw write functions for contiguous files. | ||||
| rename - A demo of SdFat::rename(old, new) and SdFile::rename(dirFile, newPath). | rename - A demo of SdFat::rename(old, new) and SdFile::rename(dirFile, newPath). | ||||
| SdFormatter - This sketch will format an SD or SDHC card. | |||||
| SdFormatter - This program will format an SD or SDHC card. | |||||
| SoftwareSpi - Simple demonstration of the SdFatSoftSpi template class. | SoftwareSpi - Simple demonstration of the SdFatSoftSpi template class. | ||||
| SdInfo - Initialize an SD card and analyze its structure for trouble shooting. | SdInfo - Initialize an SD card and analyze its structure for trouble shooting. | ||||
| StdioBench - Demo and test of stdio style stream. | StdioBench - Demo and test of stdio style stream. | ||||
| StreamParseInt - Demo of the SD.h API and the File class parseInt() function. | StreamParseInt - Demo of the SD.h API and the File class parseInt() function. | ||||
| ThreeCards - Demonstrate simultaneous use of SdFat, SdFatLibSpi, SdFatSoftSpi. | ThreeCards - Demonstrate simultaneous use of SdFat, SdFatLibSpi, SdFatSoftSpi. | ||||
| Timestamp - Sets file create, modify, and access timestamps. | Timestamp - Sets file create, modify, and access timestamps. | ||||
| TwoCards - Example using two SD cards. | TwoCards - Example using two SD cards. | ||||
| */ | |||||
| */ |
| * \param[in] path File location and name. | * \param[in] path File location and name. | ||||
| * \param[in] oflag File open mode. | * \param[in] oflag File open mode. | ||||
| */ | */ | ||||
| SdBaseFile(const char* path, uint8_t oflag) {open(path, oflag);} | |||||
| SdBaseFile(const char* path, uint8_t oflag) { | |||||
| open(path, oflag); | |||||
| } | |||||
| using FatFile::ls; | using FatFile::ls; | ||||
| using FatFile::printFatDate; | using FatFile::printFatDate; | ||||
| using FatFile::printFatTime; | using FatFile::printFatTime; | ||||
| } | } | ||||
| /** Print a file's name. | /** Print a file's name. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| size_t printName() { | size_t printName() { | ||||
| return FatFile::printName(&Serial); | return FatFile::printName(&Serial); | ||||
| return n > INT_MAX ? INT_MAX : n; | return n > INT_MAX ? INT_MAX : n; | ||||
| } | } | ||||
| /** Ensure that any bytes written to the file are saved to the SD card. */ | /** Ensure that any bytes written to the file are saved to the SD card. */ | ||||
| void flush() {SdBaseFile::sync();} | |||||
| void flush() { | |||||
| SdBaseFile::sync(); | |||||
| } | |||||
| /** Return the next available byte without consuming it. | /** Return the next available byte without consuming it. | ||||
| * | * | ||||
| * \return The byte if no error and not at eof else -1; | * \return The byte if no error and not at eof else -1; | ||||
| */ | */ | ||||
| int peek() {return SdBaseFile::peek();} | |||||
| int peek() { | |||||
| return SdBaseFile::peek(); | |||||
| } | |||||
| /** Read the next byte from a file. | /** Read the next byte from a file. | ||||
| * | * | ||||
| * \return For success return the next byte in the file as an int. | * \return For success return the next byte in the file as an int. | ||||
| * If an error occurs or end of file is reached return -1. | * If an error occurs or end of file is reached return -1. | ||||
| */ | */ | ||||
| int read() {return SdBaseFile::read();} | |||||
| int read() { | |||||
| return SdBaseFile::read(); | |||||
| } | |||||
| /** Write a byte to a file. Required by the Arduino Print class. | /** Write a byte to a file. Required by the Arduino Print class. | ||||
| * \param[in] b the byte to be written. | * \param[in] b the byte to be written. | ||||
| * Use getWriteError to check for errors. | * Use getWriteError to check for errors. | ||||
| * \return 1 for success and 0 for failure. | * \return 1 for success and 0 for failure. | ||||
| */ | */ | ||||
| size_t write(uint8_t b) {return SdBaseFile::write(b);} | |||||
| size_t write(uint8_t b) { | |||||
| return SdBaseFile::write(b); | |||||
| } | |||||
| /** Write a string to a file. Used by the Arduino Print class. | /** Write a string to a file. Used by the Arduino Print class. | ||||
| * \param[in] str Pointer to the string. | * \param[in] str Pointer to the string. | ||||
| * Use getWriteError to check for errors. | * Use getWriteError to check for errors. | ||||
| size_t write(const uint8_t *buf, size_t size) { | size_t write(const uint8_t *buf, size_t size) { | ||||
| return SdBaseFile::write(buf, size); | return SdBaseFile::write(buf, size); | ||||
| } | } | ||||
| /** Write a PROGMEM string to a file. | |||||
| * \param[in] str Pointer to the PROGMEM string. | |||||
| * Use getWriteError to check for errors. | |||||
| */ | |||||
| void write_P(PGM_P str) { | |||||
| for (uint8_t c; (c = pgm_read_byte(str)); str++) write(c); | |||||
| } | |||||
| /** Write a PROGMEM string followed by CR/LF to a file. | |||||
| * \param[in] str Pointer to the PROGMEM string. | |||||
| * Use getWriteError to check for errors. | |||||
| */ | |||||
| void writeln_P(PGM_P str) { | |||||
| write_P(str); | |||||
| write_P(PSTR("\r\n")); | |||||
| } | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| * bitwise-inclusive OR of open flags. see | * bitwise-inclusive OR of open flags. see | ||||
| * SdBaseFile::open(SdBaseFile*, const char*, uint8_t). | * SdBaseFile::open(SdBaseFile*, const char*, uint8_t). | ||||
| */ | */ | ||||
| File(const char* path, uint8_t oflag) {open(path, oflag);} | |||||
| File(const char* path, uint8_t oflag) { | |||||
| open(path, oflag); | |||||
| } | |||||
| using SdBaseFile::clearWriteError; | using SdBaseFile::clearWriteError; | ||||
| using SdBaseFile::getWriteError; | using SdBaseFile::getWriteError; | ||||
| /** The parenthesis operator. | |||||
| * | |||||
| * \return true if a file is open. | |||||
| */ | |||||
| operator bool() {return isOpen();} | |||||
| /** The parenthesis operator. | |||||
| * | |||||
| * \return true if a file is open. | |||||
| */ | |||||
| operator bool() { | |||||
| return isOpen(); | |||||
| } | |||||
| /** \return number of bytes available from the current position to EOF | /** \return number of bytes available from the current position to EOF | ||||
| * or INT_MAX if more than INT_MAX bytes are available. | * or INT_MAX if more than INT_MAX bytes are available. | ||||
| */ | */ | ||||
| return n > INT_MAX ? INT_MAX : n; | return n > INT_MAX ? INT_MAX : n; | ||||
| } | } | ||||
| /** Ensure that any bytes written to the file are saved to the SD card. */ | /** Ensure that any bytes written to the file are saved to the SD card. */ | ||||
| void flush() {SdBaseFile::sync();} | |||||
| /** This function reports if the current file is a directory or not. | |||||
| * \return true if the file is a directory. | |||||
| */ | |||||
| bool isDirectory() {return isDir();} | |||||
| /** \return a pointer to the file's name. */ | |||||
| void flush() { | |||||
| SdBaseFile::sync(); | |||||
| } | |||||
| /** This function reports if the current file is a directory or not. | |||||
| * \return true if the file is a directory. | |||||
| */ | |||||
| bool isDirectory() { | |||||
| return isDir(); | |||||
| } | |||||
| /** \return a pointer to the file's short name. */ | |||||
| char* name() { | char* name() { | ||||
| m_name[0] = 0; | m_name[0] = 0; | ||||
| getFilename(m_name); | |||||
| getSFN(m_name); | |||||
| return m_name; | return m_name; | ||||
| } | } | ||||
| /** Return the next available byte without consuming it. | /** Return the next available byte without consuming it. | ||||
| * | * | ||||
| * \return The byte if no error and not at eof else -1; | * \return The byte if no error and not at eof else -1; | ||||
| */ | */ | ||||
| int peek() {return SdBaseFile::peek();} | |||||
| int peek() { | |||||
| return SdBaseFile::peek(); | |||||
| } | |||||
| /** \return the current file position. */ | /** \return the current file position. */ | ||||
| uint32_t position() {return curPosition();} | |||||
| uint32_t position() { | |||||
| return curPosition(); | |||||
| } | |||||
| /** Opens the next file or folder in a directory. | /** Opens the next file or folder in a directory. | ||||
| * | * | ||||
| * \param[in] mode open mode flags. | * \param[in] mode open mode flags. | ||||
| * \return For success return the next byte in the file as an int. | * \return For success return the next byte in the file as an int. | ||||
| * If an error occurs or end of file is reached return -1. | * If an error occurs or end of file is reached return -1. | ||||
| */ | */ | ||||
| int read() {return SdBaseFile::read();} | |||||
| int read() { | |||||
| return SdBaseFile::read(); | |||||
| } | |||||
| /** Rewind a file if it is a directory */ | /** Rewind a file if it is a directory */ | ||||
| void rewindDirectory() { | void rewindDirectory() { | ||||
| if (isDir()) rewind(); | |||||
| if (isDir()) { | |||||
| rewind(); | |||||
| } | |||||
| } | } | ||||
| /** | /** | ||||
| * Seek to a new position in the file, which must be between | * Seek to a new position in the file, which must be between | ||||
| * \param[in] pos the new file position. | * \param[in] pos the new file position. | ||||
| * \return true for success else false. | * \return true for success else false. | ||||
| */ | */ | ||||
| bool seek(uint32_t pos) {return seekSet(pos);} | |||||
| bool seek(uint32_t pos) { | |||||
| return seekSet(pos); | |||||
| } | |||||
| /** \return the file's size. */ | /** \return the file's size. */ | ||||
| uint32_t size() {return fileSize();} | |||||
| uint32_t size() { | |||||
| return fileSize(); | |||||
| } | |||||
| /** Write a byte to a file. Required by the Arduino Print class. | /** Write a byte to a file. Required by the Arduino Print class. | ||||
| * \param[in] b the byte to be written. | * \param[in] b the byte to be written. | ||||
| * Use getWriteError to check for errors. | * Use getWriteError to check for errors. | ||||
| * \return 1 for success and 0 for failure. | * \return 1 for success and 0 for failure. | ||||
| */ | */ | ||||
| size_t write(uint8_t b) {return SdBaseFile::write(b);} | |||||
| size_t write(uint8_t b) { | |||||
| return SdBaseFile::write(b); | |||||
| } | |||||
| /** Write data to an open file. Form required by Print. | /** Write data to an open file. Form required by Print. | ||||
| * | * | ||||
| * \note Data is moved to the cache but may not be written to the | * \note Data is moved to the cache but may not be written to the |
| unsigned char always1 : 1; | unsigned char always1 : 1; | ||||
| /** CRC7 checksum */ | /** CRC7 checksum */ | ||||
| unsigned char crc : 7; | unsigned char crc : 7; | ||||
| }__attribute__((packed)) cid_t; | |||||
| } __attribute__((packed)) cid_t; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| * \class CSDV1 | * \class CSDV1 | ||||
| // byte 15 | // byte 15 | ||||
| unsigned char always1 : 1; | unsigned char always1 : 1; | ||||
| unsigned char crc : 7; | unsigned char crc : 7; | ||||
| }__attribute__((packed)) csd1_t; | |||||
| } __attribute__((packed)) csd1_t; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| * \class CSDV2 | * \class CSDV2 | ||||
| unsigned char always1 : 1; | unsigned char always1 : 1; | ||||
| /** checksum */ | /** checksum */ | ||||
| unsigned char crc : 7; | unsigned char crc : 7; | ||||
| }__attribute__((packed)) csd2_t; | |||||
| } __attribute__((packed)) csd2_t; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| * \class csd_t | * \class csd_t |
| * along with the Arduino SdSpi Library. If not, see | * along with the Arduino SdSpi Library. If not, see | ||||
| * <http://www.gnu.org/licenses/>. | * <http://www.gnu.org/licenses/>. | ||||
| */ | */ | ||||
| /** | |||||
| * \file | |||||
| * \brief SdSpi class for V2 SD/SDHC cards | |||||
| */ | |||||
| /** | |||||
| * \file | |||||
| * \brief SdSpi class for V2 SD/SDHC cards | |||||
| */ | |||||
| #ifndef SdSpi_h | #ifndef SdSpi_h | ||||
| #define SdSpi_h | #define SdSpi_h | ||||
| #include <Arduino.h> | #include <Arduino.h> | ||||
| #include "SdFatConfig.h" | #include "SdFatConfig.h" | ||||
| #include "utility/SoftSPI.h" | #include "utility/SoftSPI.h" | ||||
| #if !USE_ARDUINO_SPI_LIBRARY | |||||
| // AVR Arduinos | |||||
| #ifdef __AVR__ | |||||
| #if AVR_SOFT_SPI | |||||
| #define USE_SOFTWARE_SPI 1 | |||||
| #elif LEONARDO_SOFT_SPI && defined(__AVR_ATmega32U4__) && !defined(CORE_TEENSY) | |||||
| #define USE_SOFTWARE_SPI 1 | |||||
| #elif MEGA_SOFT_SPI && (defined(__AVR_ATmega1280__)\ | |||||
| || defined(__AVR_ATmega2560__)) | |||||
| #define USE_SOFTWARE_SPI 1 | |||||
| #endif // USE_SOFTWARE_SPI | |||||
| #endif // __AVR__ | |||||
| // Due | |||||
| #if DUE_SOFT_SPI && defined(__arm__) && !defined(CORE_TEENSY) | |||||
| #define USE_SOFTWARE_SPI 1 | |||||
| #endif // DUE_SOFT_SPI && defined(__arm__) && !defined(CORE_TEENSY) | |||||
| // Teensy 3.x | |||||
| #if TEENSY3_SOFT_SPI && defined(__arm__) && defined(CORE_TEENSY) | |||||
| #define USE_SOFTWARE_SPI 1 | |||||
| #endif // TEENSY3_SOFT_SPI && defined(__arm__) && defined(CORE_TEENSY) | |||||
| #endif // !USE_ARDUINO_SPI_LIBRARY | |||||
| #ifndef USE_SOFTWARE_SPI | |||||
| /** Default is no software SPI */ | |||||
| #define USE_SOFTWARE_SPI 0 | |||||
| #endif // USE_SOFTWARE_SPI | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** | /** | ||||
| * \class SdSpiBase | * \class SdSpiBase | ||||
| * \param[in] data Byte to send | * \param[in] data Byte to send | ||||
| */ | */ | ||||
| virtual void send(uint8_t data) = 0; | virtual void send(uint8_t data) = 0; | ||||
| /** Send multiple bytes. | |||||
| * | |||||
| * \param[in] buf Buffer for data to be sent. | |||||
| * \param[in] n Number of bytes to send. | |||||
| */ | |||||
| /** Send multiple bytes. | |||||
| * | |||||
| * \param[in] buf Buffer for data to be sent. | |||||
| * \param[in] n Number of bytes to send. | |||||
| */ | |||||
| virtual void send(const uint8_t* buf, size_t n) = 0; | virtual void send(const uint8_t* buf, size_t n) = 0; | ||||
| /** \return true if hardware SPI else false */ | /** \return true if hardware SPI else false */ | ||||
| virtual bool useSpiTransactions() = 0; | virtual bool useSpiTransactions() = 0; | ||||
| * \class SdSpi | * \class SdSpi | ||||
| * \brief SPI class for access to SD and SDHC flash memory cards. | * \brief SPI class for access to SD and SDHC flash memory cards. | ||||
| */ | */ | ||||
| #if USE_MULTIPLE_SPI_TYPES | |||||
| #if SD_SPI_CONFIGURATION >= 3 | |||||
| class SdSpi : public SdSpiBase { | class SdSpi : public SdSpiBase { | ||||
| #else | |||||
| #else // SD_SPI_CONFIGURATION >= 3 | |||||
| class SdSpi { | class SdSpi { | ||||
| #endif | |||||
| #endif // SD_SPI_CONFIGURATION >= 3 | |||||
| public: | public: | ||||
| /** Initialize the SPI bus */ | /** Initialize the SPI bus */ | ||||
| void begin(); | void begin(); | ||||
| */ | */ | ||||
| void send(const uint8_t* buf, size_t n); | void send(const uint8_t* buf, size_t n); | ||||
| /** \return true - uses SPI transactions */ | /** \return true - uses SPI transactions */ | ||||
| bool useSpiTransactions() {return true;} | |||||
| bool useSpiTransactions() { | |||||
| return true; | |||||
| } | |||||
| }; | }; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| #if USE_MULTIPLE_SPI_TYPES || USE_ARDUINO_SPI_LIBRARY || defined(DOXYGEN) | |||||
| #include <SPI.h> | |||||
| /** | /** | ||||
| * \class SdSpiLib | * \class SdSpiLib | ||||
| * \brief Arduino SPI library class for access to SD and SDHC flash | * \brief Arduino SPI library class for access to SD and SDHC flash | ||||
| * memory cards. | * memory cards. | ||||
| */ | */ | ||||
| #if USE_MULTIPLE_SPI_TYPES | |||||
| #if SD_SPI_CONFIGURATION >= 3 || SD_SPI_CONFIGURATION == 1 || defined(DOXYGEN) | |||||
| #include <SPI.h> | |||||
| #if SD_SPI_CONFIGURATION >= 3 | |||||
| class SdSpiLib : public SdSpiBase { | class SdSpiLib : public SdSpiBase { | ||||
| #else | |||||
| #else // SD_SPI_CONFIGURATION >= 3 | |||||
| class SdSpiLib { | class SdSpiLib { | ||||
| #endif | |||||
| #endif // SD_SPI_CONFIGURATION >= 3 | |||||
| public: | public: | ||||
| /** | /** | ||||
| * Initialize SPI pins. | * Initialize SPI pins. | ||||
| */ | */ | ||||
| void begin() {SPI.begin();} | |||||
| void begin() { | |||||
| SPI.begin(); | |||||
| } | |||||
| /** Set SPI options for access to SD/SDHC cards. | /** Set SPI options for access to SD/SDHC cards. | ||||
| * | * | ||||
| * \param[in] divisor SCK clock divider relative to the system clock. | * \param[in] divisor SCK clock divider relative to the system clock. | ||||
| void init(uint8_t divisor) { | void init(uint8_t divisor) { | ||||
| SPI.setBitOrder(MSBFIRST); | SPI.setBitOrder(MSBFIRST); | ||||
| SPI.setDataMode(SPI_MODE0); | SPI.setDataMode(SPI_MODE0); | ||||
| #ifndef SPI_CLOCK_DIV128 | |||||
| #ifndef SPI_CLOCK_DIV128 | |||||
| SPI.setClockDivider(divisor); | SPI.setClockDivider(divisor); | ||||
| #else // SPI_CLOCK_DIV128 | |||||
| #else // SPI_CLOCK_DIV128 | |||||
| int v; | int v; | ||||
| if (divisor <= 2) v = SPI_CLOCK_DIV2; | |||||
| else if (divisor <= 4) v = SPI_CLOCK_DIV4; | |||||
| else if (divisor <= 8) v = SPI_CLOCK_DIV8; | |||||
| else if (divisor <= 16) v = SPI_CLOCK_DIV16; | |||||
| else if (divisor <= 32) v = SPI_CLOCK_DIV32; | |||||
| else if (divisor <= 64) v = SPI_CLOCK_DIV64; | |||||
| else v = SPI_CLOCK_DIV128; | |||||
| if (divisor <= 2) { | |||||
| v = SPI_CLOCK_DIV2; | |||||
| } else if (divisor <= 4) { | |||||
| v = SPI_CLOCK_DIV4; | |||||
| } else if (divisor <= 8) { | |||||
| v = SPI_CLOCK_DIV8; | |||||
| } else if (divisor <= 16) { | |||||
| v = SPI_CLOCK_DIV16; | |||||
| } else if (divisor <= 32) { | |||||
| v = SPI_CLOCK_DIV32; | |||||
| } else if (divisor <= 64) { | |||||
| v = SPI_CLOCK_DIV64; | |||||
| } else { | |||||
| v = SPI_CLOCK_DIV128; | |||||
| } | |||||
| SPI.setClockDivider(v); | SPI.setClockDivider(v); | ||||
| #endif // SPI_CLOCK_DIV128 | |||||
| #endif // SPI_CLOCK_DIV128 | |||||
| } | } | ||||
| /** Receive a byte. | /** Receive a byte. | ||||
| * | * | ||||
| * \return The byte. | * \return The byte. | ||||
| */ | */ | ||||
| uint8_t receive() {return SPI.transfer(0XFF);} | |||||
| uint8_t receive() { | |||||
| return SPI.transfer(0XFF); | |||||
| } | |||||
| /** Receive multiple bytes. | /** Receive multiple bytes. | ||||
| * | * | ||||
| * \param[out] buf Buffer to receive the data. | * \param[out] buf Buffer to receive the data. | ||||
| } | } | ||||
| } | } | ||||
| /** \return true - uses SPI transactions */ | /** \return true - uses SPI transactions */ | ||||
| bool useSpiTransactions() {return true;} | |||||
| bool useSpiTransactions() { | |||||
| return true; | |||||
| } | |||||
| }; | }; | ||||
| #endif // USE_MULTIPLE_SPI_TYPES || USE_ARDUINO_SPI_LIBRARY | |||||
| #endif // SD_SPI_CONFIGURATION >= 3 || SD_SPI_CONFIGURATION == 1 | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** | /** | ||||
| * \class SdSpiSoft | * \class SdSpiSoft | ||||
| * \brief Software SPI class for access to SD and SDHC flash memory cards. | * \brief Software SPI class for access to SD and SDHC flash memory cards. | ||||
| */ | */ | ||||
| template<uint8_t MisoPin, uint8_t MosiPin, uint8_t SckPin> | template<uint8_t MisoPin, uint8_t MosiPin, uint8_t SckPin> | ||||
| #if USE_MULTIPLE_SPI_TYPES | |||||
| class SdSpiSoft : public SdSpiBase { | class SdSpiSoft : public SdSpiBase { | ||||
| #else | |||||
| class SdSpiSoft { | |||||
| #endif | |||||
| public: | public: | ||||
| /** | /** | ||||
| * initialize SPI pins | * initialize SPI pins | ||||
| */ | */ | ||||
| void begin() {m_spi.begin();} | |||||
| void begin() { | |||||
| m_spi.begin(); | |||||
| } | |||||
| /** | /** | ||||
| * Initialize hardware SPI - dummy for soft SPI | * Initialize hardware SPI - dummy for soft SPI | ||||
| * \param[in] divisor SCK divisor - ignored. | * \param[in] divisor SCK divisor - ignored. | ||||
| * | * | ||||
| * \return The byte. | * \return The byte. | ||||
| */ | */ | ||||
| uint8_t receive() {return m_spi.receive();} | |||||
| /** Receive multiple bytes. | |||||
| * | |||||
| * \param[out] buf Buffer to receive the data. | |||||
| * \param[in] n Number of bytes to receive. | |||||
| * | |||||
| * \return Zero for no error or nonzero error code. | |||||
| */ | |||||
| uint8_t receive() { | |||||
| return m_spi.receive(); | |||||
| } | |||||
| /** Receive multiple bytes. | |||||
| * | |||||
| * \param[out] buf Buffer to receive the data. | |||||
| * \param[in] n Number of bytes to receive. | |||||
| * | |||||
| * \return Zero for no error or nonzero error code. | |||||
| */ | |||||
| uint8_t receive(uint8_t* buf, size_t n) { | uint8_t receive(uint8_t* buf, size_t n) { | ||||
| for (size_t i = 0; i < n; i++) { | for (size_t i = 0; i < n; i++) { | ||||
| buf[i] = receive(); | buf[i] = receive(); | ||||
| * | * | ||||
| * \param[in] data Byte to send | * \param[in] data Byte to send | ||||
| */ | */ | ||||
| void send(uint8_t data) {m_spi.send(data);} | |||||
| void send(uint8_t data) { | |||||
| m_spi.send(data); | |||||
| } | |||||
| /** Send multiple bytes. | /** Send multiple bytes. | ||||
| * | * | ||||
| * \param[in] buf Buffer for data to be sent. | * \param[in] buf Buffer for data to be sent. | ||||
| } | } | ||||
| } | } | ||||
| /** \return false - no SPI transactions */ | /** \return false - no SPI transactions */ | ||||
| bool useSpiTransactions() {return false;} | |||||
| bool useSpiTransactions() { | |||||
| return false; | |||||
| } | |||||
| private: | private: | ||||
| SoftSPI<MisoPin, MosiPin, SckPin, 0> m_spi; | SoftSPI<MisoPin, MosiPin, SckPin, 0> m_spi; | ||||
| }; | }; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| #if USE_ARDUINO_SPI_LIBRARY | |||||
| #if SD_SPI_CONFIGURATION == 0 || SD_SPI_CONFIGURATION >= 3 | |||||
| /** Default is custom fast SPI. */ | |||||
| typedef SdSpi SpiDefault_t; | |||||
| #elif SD_SPI_CONFIGURATION == 1 | |||||
| /** Default is Arduino library SPI. */ | /** Default is Arduino library SPI. */ | ||||
| typedef SdSpiLib SpiDefault_t; | typedef SdSpiLib SpiDefault_t; | ||||
| #elif USE_SOFTWARE_SPI | |||||
| #elif SD_SPI_CONFIGURATION == 2 | |||||
| /** Default is software SPI. */ | /** Default is software SPI. */ | ||||
| typedef SdSpiSoft<SOFT_SPI_MISO_PIN, SOFT_SPI_MOSI_PIN, SOFT_SPI_SCK_PIN> | typedef SdSpiSoft<SOFT_SPI_MISO_PIN, SOFT_SPI_MOSI_PIN, SOFT_SPI_SCK_PIN> | ||||
| SpiDefault_t; | |||||
| #else // USE_ARDUINO_SPI_LIBRARY | |||||
| /** Default is custom fast SPI. */ | |||||
| typedef SdSpi SpiDefault_t; | |||||
| #endif | |||||
| SpiDefault_t; | |||||
| #else // SD_SPI_CONFIGURATION == 0 || SD_SPI_CONFIGURATION >= 3 | |||||
| #error bad SD_SPI_CONFIGURATION | |||||
| #endif // SD_SPI_CONFIGURATION == 0 || SD_SPI_CONFIGURATION >= 3 | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // Use of in-line for AVR to save flash. | // Use of in-line for AVR to save flash. | ||||
| #ifdef __AVR__ | #ifdef __AVR__ | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| inline uint8_t SdSpi::receive(uint8_t* buf, size_t n) { | inline uint8_t SdSpi::receive(uint8_t* buf, size_t n) { | ||||
| if (n-- == 0) return 0; | |||||
| if (n-- == 0) { | |||||
| return 0; | |||||
| } | |||||
| SPDR = 0XFF; | SPDR = 0XFF; | ||||
| for (size_t i = 0; i < n; i++) { | for (size_t i = 0; i < n; i++) { | ||||
| while (!(SPSR & (1 << SPIF))) {} | while (!(SPSR & (1 << SPIF))) {} | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| inline void SdSpi::send(const uint8_t* buf , size_t n) { | inline void SdSpi::send(const uint8_t* buf , size_t n) { | ||||
| if (n == 0) return; | |||||
| if (n == 0) { | |||||
| return; | |||||
| } | |||||
| SPDR = buf[0]; | SPDR = buf[0]; | ||||
| if (n > 1) { | if (n > 1) { | ||||
| uint8_t b = buf[1]; | uint8_t b = buf[1]; | ||||
| while (1) { | while (1) { | ||||
| while (!(SPSR & (1 << SPIF))) {} | while (!(SPSR & (1 << SPIF))) {} | ||||
| SPDR = b; | SPDR = b; | ||||
| if (i == n) break; | |||||
| if (i == n) { | |||||
| break; | |||||
| } | |||||
| b = buf[i++]; | b = buf[i++]; | ||||
| } | } | ||||
| } | } |
| */ | */ | ||||
| #include "SdSpiCard.h" | #include "SdSpiCard.h" | ||||
| #include "SdSpi.h" | #include "SdSpi.h" | ||||
| #if ENABLE_SPI_TRANSACTION && defined(SPI_HAS_TRANSACTION) | |||||
| #if ENABLE_SPI_TRANSACTION | |||||
| #include <SPI.h> | #include <SPI.h> | ||||
| #endif // ENABLE_SPI_TRANSACTION && defined(SPI_HAS_TRANSACTION) | |||||
| #endif // ENABLE_SPI_TRANSACTION | |||||
| // debug trace macro | // debug trace macro | ||||
| #define SD_TRACE(m, b) | #define SD_TRACE(m, b) | ||||
| // #define SD_TRACE(m, b) Serial.print(m);Serial.println(b); | // #define SD_TRACE(m, b) Serial.print(m);Serial.println(b); | ||||
| uint8_t d = data[i]; | uint8_t d = data[i]; | ||||
| for (uint8_t j = 0; j < 8; j++) { | for (uint8_t j = 0; j < 8; j++) { | ||||
| crc <<= 1; | crc <<= 1; | ||||
| if ((d & 0x80) ^ (crc & 0x80)) crc ^= 0x09; | |||||
| if ((d & 0x80) ^ (crc & 0x80)) { | |||||
| crc ^= 0x09; | |||||
| } | |||||
| d <<= 1; | d <<= 1; | ||||
| } | } | ||||
| } | } | ||||
| spiInit(m_sckDivisor); | spiInit(m_sckDivisor); | ||||
| // must supply min of 74 clock cycles with CS high. | // must supply min of 74 clock cycles with CS high. | ||||
| for (uint8_t i = 0; i < 10; i++) spiSend(0XFF); | |||||
| for (uint8_t i = 0; i < 10; i++) { | |||||
| spiSend(0XFF); | |||||
| } | |||||
| // command to go idle in SPI mode | // command to go idle in SPI mode | ||||
| while (cardCommand(CMD0, 0) != R1_IDLE_STATE) { | while (cardCommand(CMD0, 0) != R1_IDLE_STATE) { | ||||
| type(SD_CARD_TYPE_SD1); | type(SD_CARD_TYPE_SD1); | ||||
| break; | break; | ||||
| } | } | ||||
| for (uint8_t i = 0; i < 4; i++) m_status = spiReceive(); | |||||
| for (uint8_t i = 0; i < 4; i++) { | |||||
| m_status = spiReceive(); | |||||
| } | |||||
| if (m_status == 0XAA) { | if (m_status == 0XAA) { | ||||
| type(SD_CARD_TYPE_SD2); | type(SD_CARD_TYPE_SD2); | ||||
| break; | break; | ||||
| error(SD_CARD_ERROR_CMD58); | error(SD_CARD_ERROR_CMD58); | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| if ((spiReceive() & 0XC0) == 0XC0) type(SD_CARD_TYPE_SDHC); | |||||
| if ((spiReceive() & 0XC0) == 0XC0) { | |||||
| type(SD_CARD_TYPE_SDHC); | |||||
| } | |||||
| // Discard rest of ocr - contains allowed voltage range. | // Discard rest of ocr - contains allowed voltage range. | ||||
| for (uint8_t i = 0; i < 3; i++) spiReceive(); | |||||
| for (uint8_t i = 0; i < 3; i++) { | |||||
| spiReceive(); | |||||
| } | |||||
| } | } | ||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| m_sckDivisor = sckDivisor; | m_sckDivisor = sckDivisor; | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; | ||||
| } | } | ||||
| d[5] = CRC7(d, 5); | d[5] = CRC7(d, 5); | ||||
| // send message | // send message | ||||
| for (uint8_t k = 0; k < 6; k++) spiSend(d[k]); | |||||
| for (uint8_t k = 0; k < 6; k++) { | |||||
| spiSend(d[k]); | |||||
| } | |||||
| #else // USE_SD_CRC | #else // USE_SD_CRC | ||||
| // send command | // send command | ||||
| spiSend(cmd | 0x40); | spiSend(cmd | 0x40); | ||||
| // send argument | // send argument | ||||
| for (int8_t i = 3; i >= 0; i--) spiSend(pa[i]); | |||||
| for (int8_t i = 3; i >= 0; i--) { | |||||
| spiSend(pa[i]); | |||||
| } | |||||
| // send CRC - correct for CMD0 with arg zero or CMD8 with arg 0X1AA | // send CRC - correct for CMD0 with arg zero or CMD8 with arg 0X1AA | ||||
| spiSend(cmd == CMD0 ? 0X95 : 0X87); | spiSend(cmd == CMD0 ? 0X95 : 0X87); | ||||
| #endif // USE_SD_CRC | #endif // USE_SD_CRC | ||||
| // skip stuff byte for stop read | // skip stuff byte for stop read | ||||
| if (cmd == CMD12) spiReceive(); | |||||
| if (cmd == CMD12) { | |||||
| spiReceive(); | |||||
| } | |||||
| // wait for response | // wait for response | ||||
| for (uint8_t i = 0; ((m_status = spiReceive()) & 0X80) && i != 0XFF; i++) { | for (uint8_t i = 0; ((m_status = spiReceive()) & 0X80) && i != 0XFF; i++) { | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| uint32_t SdSpiCard::cardSize() { | uint32_t SdSpiCard::cardSize() { | ||||
| csd_t csd; | csd_t csd; | ||||
| if (!readCSD(&csd)) return 0; | |||||
| if (!readCSD(&csd)) { | |||||
| return 0; | |||||
| } | |||||
| if (csd.v1.csd_ver == 0) { | if (csd.v1.csd_ver == 0) { | ||||
| uint8_t read_bl_len = csd.v1.read_bl_len; | uint8_t read_bl_len = csd.v1.read_bl_len; | ||||
| uint16_t c_size = (csd.v1.c_size_high << 10) | uint16_t c_size = (csd.v1.c_size_high << 10) | ||||
| // insure MISO goes high impedance | // insure MISO goes high impedance | ||||
| spiSend(0XFF); | spiSend(0XFF); | ||||
| #if ENABLE_SPI_TRANSACTION && defined(SPI_HAS_TRANSACTION) | #if ENABLE_SPI_TRANSACTION && defined(SPI_HAS_TRANSACTION) | ||||
| if (useSpiTransactions()) SPI.endTransaction(); | |||||
| if (useSpiTransactions()) { | |||||
| SPI.endTransaction(); | |||||
| } | |||||
| #endif // ENABLE_SPI_TRANSACTION && defined(SPI_HAS_TRANSACTION) | #endif // ENABLE_SPI_TRANSACTION && defined(SPI_HAS_TRANSACTION) | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void SdSpiCard::chipSelectLow() { | void SdSpiCard::chipSelectLow() { | ||||
| #if ENABLE_SPI_TRANSACTION && defined(SPI_HAS_TRANSACTION) | #if ENABLE_SPI_TRANSACTION && defined(SPI_HAS_TRANSACTION) | ||||
| if (useSpiTransactions()) SPI.beginTransaction(SPISettings()); | |||||
| if (useSpiTransactions()) { | |||||
| SPI.beginTransaction(SPISettings()); | |||||
| } | |||||
| #endif // ENABLE_SPI_TRANSACTION && defined(SPI_HAS_TRANSACTION) | #endif // ENABLE_SPI_TRANSACTION && defined(SPI_HAS_TRANSACTION) | ||||
| spiInit(m_sckDivisor); | spiInit(m_sckDivisor); | ||||
| digitalWrite(m_chipSelectPin, LOW); | digitalWrite(m_chipSelectPin, LOW); | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| bool SdSpiCard::erase(uint32_t firstBlock, uint32_t lastBlock) { | bool SdSpiCard::erase(uint32_t firstBlock, uint32_t lastBlock) { | ||||
| csd_t csd; | csd_t csd; | ||||
| if (!readCSD(&csd)) goto fail; | |||||
| if (!readCSD(&csd)) { | |||||
| goto fail; | |||||
| } | |||||
| // check for single block erase | // check for single block erase | ||||
| if (!csd.v1.erase_blk_en) { | if (!csd.v1.erase_blk_en) { | ||||
| // erase size mask | // erase size mask | ||||
| lastBlock <<= 9; | lastBlock <<= 9; | ||||
| } | } | ||||
| if (cardCommand(CMD32, firstBlock) | if (cardCommand(CMD32, firstBlock) | ||||
| || cardCommand(CMD33, lastBlock) | |||||
| || cardCommand(CMD38, 0)) { | |||||
| error(SD_CARD_ERROR_ERASE); | |||||
| goto fail; | |||||
| || cardCommand(CMD33, lastBlock) | |||||
| || cardCommand(CMD38, 0)) { | |||||
| error(SD_CARD_ERROR_ERASE); | |||||
| goto fail; | |||||
| } | } | ||||
| if (!waitNotBusy(SD_ERASE_TIMEOUT)) { | if (!waitNotBusy(SD_ERASE_TIMEOUT)) { | ||||
| error(SD_CARD_ERROR_ERASE_TIMEOUT); | error(SD_CARD_ERROR_ERASE_TIMEOUT); | ||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; | ||||
| } | } | ||||
| chipSelectLow(); | chipSelectLow(); | ||||
| for (uint8_t i = 0; i < 8; i++) { | for (uint8_t i = 0; i < 8; i++) { | ||||
| rtn = spiReceive() != 0XFF; | rtn = spiReceive() != 0XFF; | ||||
| if (!rtn) break; | |||||
| if (!rtn) { | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return rtn; | return rtn; | ||||
| bool SdSpiCard::readBlock(uint32_t blockNumber, uint8_t* dst) { | bool SdSpiCard::readBlock(uint32_t blockNumber, uint8_t* dst) { | ||||
| SD_TRACE("RB", blockNumber); | SD_TRACE("RB", blockNumber); | ||||
| // use address if not SDHC card | // use address if not SDHC card | ||||
| if (type()!= SD_CARD_TYPE_SDHC) blockNumber <<= 9; | |||||
| if (type()!= SD_CARD_TYPE_SDHC) { | |||||
| blockNumber <<= 9; | |||||
| } | |||||
| if (cardCommand(CMD17, blockNumber)) { | if (cardCommand(CMD17, blockNumber)) { | ||||
| error(SD_CARD_ERROR_CMD17); | error(SD_CARD_ERROR_CMD17); | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| return readData(dst, 512); | return readData(dst, 512); | ||||
| fail: | |||||
| fail: | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| bool SdSpiCard::readBlocks(uint32_t block, uint8_t* dst, size_t count) { | bool SdSpiCard::readBlocks(uint32_t block, uint8_t* dst, size_t count) { | ||||
| if (!readStart(block)) return false; | |||||
| if (!readStart(block)) { | |||||
| return false; | |||||
| } | |||||
| for (uint16_t b = 0; b < count; b++, dst += 512) { | for (uint16_t b = 0; b < count; b++, dst += 512) { | ||||
| if (!readData(dst)) return false; | |||||
| if (!readData(dst)) { | |||||
| return false; | |||||
| } | |||||
| } | } | ||||
| return readStop(); | return readStop(); | ||||
| } | } | ||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; | ||||
| } | } | ||||
| error(SD_CARD_ERROR_CMD58); | error(SD_CARD_ERROR_CMD58); | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| for (uint8_t i = 0; i < 4; i++) p[3-i] = spiReceive(); | |||||
| for (uint8_t i = 0; i < 4; i++) { | |||||
| p[3-i] = spiReceive(); | |||||
| } | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| return readData(dst, 16); | return readData(dst, 16); | ||||
| fail: | |||||
| fail: | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| bool SdSpiCard::readStart(uint32_t blockNumber) { | bool SdSpiCard::readStart(uint32_t blockNumber) { | ||||
| SD_TRACE("RS", blockNumber); | SD_TRACE("RS", blockNumber); | ||||
| if (type()!= SD_CARD_TYPE_SDHC) blockNumber <<= 9; | |||||
| if (type()!= SD_CARD_TYPE_SDHC) { | |||||
| blockNumber <<= 9; | |||||
| } | |||||
| if (cardCommand(CMD18, blockNumber)) { | if (cardCommand(CMD18, blockNumber)) { | ||||
| error(SD_CARD_ERROR_CMD18); | error(SD_CARD_ERROR_CMD18); | ||||
| goto fail; | goto fail; | ||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; | ||||
| } | } | ||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; | ||||
| } | } | ||||
| bool SdSpiCard::waitNotBusy(uint16_t timeoutMillis) { | bool SdSpiCard::waitNotBusy(uint16_t timeoutMillis) { | ||||
| uint16_t t0 = millis(); | uint16_t t0 = millis(); | ||||
| while (spiReceive() != 0XFF) { | while (spiReceive() != 0XFF) { | ||||
| if (((uint16_t)millis() - t0) >= timeoutMillis) goto fail; | |||||
| if (((uint16_t)millis() - t0) >= timeoutMillis) { | |||||
| goto fail; | |||||
| } | |||||
| spiYield(); | spiYield(); | ||||
| } | } | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| bool SdSpiCard::writeBlock(uint32_t blockNumber, const uint8_t* src) { | bool SdSpiCard::writeBlock(uint32_t blockNumber, const uint8_t* src) { | ||||
| SD_TRACE("WB", blockNumber); | SD_TRACE("WB", blockNumber); | ||||
| // use address if not SDHC card | // use address if not SDHC card | ||||
| if (type() != SD_CARD_TYPE_SDHC) blockNumber <<= 9; | |||||
| if (type() != SD_CARD_TYPE_SDHC) { | |||||
| blockNumber <<= 9; | |||||
| } | |||||
| if (cardCommand(CMD24, blockNumber)) { | if (cardCommand(CMD24, blockNumber)) { | ||||
| error(SD_CARD_ERROR_CMD24); | error(SD_CARD_ERROR_CMD24); | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| if (!writeData(DATA_START_BLOCK, src)) goto fail; | |||||
| if (!writeData(DATA_START_BLOCK, src)) { | |||||
| goto fail; | |||||
| } | |||||
| #define CHECK_PROGRAMMING 0 | #define CHECK_PROGRAMMING 0 | ||||
| #if CHECK_PROGRAMMING | #if CHECK_PROGRAMMING | ||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| bool SdSpiCard::writeBlocks(uint32_t block, const uint8_t* src, size_t count) { | bool SdSpiCard::writeBlocks(uint32_t block, const uint8_t* src, size_t count) { | ||||
| if (!writeStart(block, count)) return false; | |||||
| if (!writeStart(block, count)) { | |||||
| return false; | |||||
| } | |||||
| for (size_t b = 0; b < count; b++, src += 512) { | for (size_t b = 0; b < count; b++, src += 512) { | ||||
| if (!writeData(src)) return false; | |||||
| if (!writeData(src)) { | |||||
| return false; | |||||
| } | |||||
| } | } | ||||
| return writeStop(); | return writeStop(); | ||||
| } | } | ||||
| bool SdSpiCard::writeData(const uint8_t* src) { | bool SdSpiCard::writeData(const uint8_t* src) { | ||||
| chipSelectLow(); | chipSelectLow(); | ||||
| // wait for previous write to finish | // wait for previous write to finish | ||||
| if (!waitNotBusy(SD_WRITE_TIMEOUT)) goto fail; | |||||
| if (!writeData(WRITE_MULTIPLE_TOKEN, src)) goto fail; | |||||
| if (!waitNotBusy(SD_WRITE_TIMEOUT)) { | |||||
| goto fail; | |||||
| } | |||||
| if (!writeData(WRITE_MULTIPLE_TOKEN, src)) { | |||||
| goto fail; | |||||
| } | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| error(SD_CARD_ERROR_WRITE_MULTIPLE); | error(SD_CARD_ERROR_WRITE_MULTIPLE); | ||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; | ||||
| } | } | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; | ||||
| } | } | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| // use address if not SDHC card | // use address if not SDHC card | ||||
| if (type() != SD_CARD_TYPE_SDHC) blockNumber <<= 9; | |||||
| if (type() != SD_CARD_TYPE_SDHC) { | |||||
| blockNumber <<= 9; | |||||
| } | |||||
| if (cardCommand(CMD25, blockNumber)) { | if (cardCommand(CMD25, blockNumber)) { | ||||
| error(SD_CARD_ERROR_CMD25); | error(SD_CARD_ERROR_CMD25); | ||||
| goto fail; | goto fail; | ||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| bool SdSpiCard::writeStop() { | bool SdSpiCard::writeStop() { | ||||
| chipSelectLow(); | chipSelectLow(); | ||||
| if (!waitNotBusy(SD_WRITE_TIMEOUT)) goto fail; | |||||
| if (!waitNotBusy(SD_WRITE_TIMEOUT)) { | |||||
| goto fail; | |||||
| } | |||||
| spiSend(STOP_TRAN_TOKEN); | spiSend(STOP_TRAN_TOKEN); | ||||
| if (!waitNotBusy(SD_WRITE_TIMEOUT)) goto fail; | |||||
| if (!waitNotBusy(SD_WRITE_TIMEOUT)) { | |||||
| goto fail; | |||||
| } | |||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| error(SD_CARD_ERROR_STOP_TRAN); | error(SD_CARD_ERROR_STOP_TRAN); | ||||
| chipSelectHigh(); | chipSelectHigh(); | ||||
| return false; | return false; |
| class SdSpiCard { | class SdSpiCard { | ||||
| public: | public: | ||||
| /** typedef for SPI class. */ | /** typedef for SPI class. */ | ||||
| #if USE_MULTIPLE_SPI_TYPES | |||||
| typedef SdSpiBase m_spi_t; | |||||
| #else // USE_MULTIPLE_SPI_TYPES | |||||
| #if SD_SPI_CONFIGURATION < 3 | |||||
| typedef SpiDefault_t m_spi_t; | typedef SpiDefault_t m_spi_t; | ||||
| #endif // USE_MULTIPLE_SPI_TYPES | |||||
| #else // SD_SPI_CONFIGURATION < 3 | |||||
| typedef SdSpiBase m_spi_t; | |||||
| #endif // SD_SPI_CONFIGURATION < 3 | |||||
| /** Construct an instance of SdSpiCard. */ | /** Construct an instance of SdSpiCard. */ | ||||
| SdSpiCard() : m_errorCode(SD_CARD_ERROR_INIT_NOT_CALLED), m_type(0) {} | SdSpiCard() : m_errorCode(SD_CARD_ERROR_INIT_NOT_CALLED), m_type(0) {} | ||||
| /** Initialize the SD card. | /** Initialize the SD card. | ||||
| * \return true for success else false. | * \return true for success else false. | ||||
| */ | */ | ||||
| bool begin(m_spi_t* spi, uint8_t chipSelectPin = SS, | bool begin(m_spi_t* spi, uint8_t chipSelectPin = SS, | ||||
| uint8_t sckDivisor = SPI_FULL_SPEED); | |||||
| uint8_t sckDivisor = SPI_FULL_SPEED); | |||||
| /** | /** | ||||
| * Determine the size of an SD flash memory card. | * Determine the size of an SD flash memory card. | ||||
| * | * | ||||
| * either 0 or 1, depends on the card vendor. The card must support | * either 0 or 1, depends on the card vendor. The card must support | ||||
| * single block erase. | * single block erase. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool erase(uint32_t firstBlock, uint32_t lastBlock); | bool erase(uint32_t firstBlock, uint32_t lastBlock); | ||||
| /** Determine if card supports single block erase. | /** Determine if card supports single block erase. | ||||
| * Set SD error code. | * Set SD error code. | ||||
| * \param[in] code value for error code. | * \param[in] code value for error code. | ||||
| */ | */ | ||||
| void error(uint8_t code) {m_errorCode = code;} | |||||
| void error(uint8_t code) { | |||||
| m_errorCode = code; | |||||
| } | |||||
| /** | /** | ||||
| * \return code for the last error. See SdSpiCard.h for a list of error codes. | * \return code for the last error. See SdSpiCard.h for a list of error codes. | ||||
| */ | */ | ||||
| int errorCode() const {return m_errorCode;} | |||||
| int errorCode() const { | |||||
| return m_errorCode; | |||||
| } | |||||
| /** \return error data for last error. */ | /** \return error data for last error. */ | ||||
| int errorData() const {return m_status;} | |||||
| int errorData() const { | |||||
| return m_status; | |||||
| } | |||||
| /** | /** | ||||
| * Check for busy. MISO low indicates the card is busy. | * Check for busy. MISO low indicates the card is busy. | ||||
| * | * | ||||
| * | * | ||||
| * \param[in] block Logical block to be read. | * \param[in] block Logical block to be read. | ||||
| * \param[out] dst Pointer to the location that will receive the data. | * \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. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool readBlock(uint32_t block, uint8_t* dst); | bool readBlock(uint32_t block, uint8_t* dst); | ||||
| /** | /** | ||||
| * \param[in] block Logical block to be read. | * \param[in] block Logical block to be read. | ||||
| * \param[in] count Number of blocks to be read. | * \param[in] count Number of blocks to be read. | ||||
| * \param[out] dst Pointer to the location that will receive the data. | * \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. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool readBlocks(uint32_t block, uint8_t* dst, size_t count); | bool readBlocks(uint32_t block, uint8_t* dst, size_t count); | ||||
| /** | /** | ||||
| * | * | ||||
| * \param[out] dst Pointer to the location for the data to be read. | * \param[out] dst Pointer to the location for the data to be read. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool readData(uint8_t *dst); | bool readData(uint8_t *dst); | ||||
| /** Read OCR register. | /** Read OCR register. | ||||
| * \note This function is used with readData() and readStop() for optimized | * \note This function is used with readData() and readStop() for optimized | ||||
| * multiple block reads. SPI chipSelect must be low for the entire sequence. | * multiple block reads. SPI chipSelect must be low for the entire sequence. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool readStart(uint32_t blockNumber); | bool readStart(uint32_t blockNumber); | ||||
| /** End a read multiple blocks sequence. | /** End a read multiple blocks sequence. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool readStop(); | bool readStop(); | ||||
| /** Return SCK divisor. | /** Return SCK divisor. | ||||
| * | * | ||||
| * \return Requested SCK divisor. | * \return Requested SCK divisor. | ||||
| */ | */ | ||||
| uint8_t sckDivisor() {return m_sckDivisor;} | |||||
| uint8_t sckDivisor() { | |||||
| return m_sckDivisor; | |||||
| } | |||||
| /** Return the card type: SD V1, SD V2 or SDHC | /** Return the card type: SD V1, SD V2 or SDHC | ||||
| * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC. | * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC. | ||||
| */ | */ | ||||
| int type() const {return m_type;} | |||||
| int type() const { | |||||
| return m_type; | |||||
| } | |||||
| /** | /** | ||||
| * Writes a 512 byte block to an SD card. | * Writes a 512 byte block to an SD card. | ||||
| * | * | ||||
| * \param[in] blockNumber Logical block to be written. | * \param[in] blockNumber Logical block to be written. | ||||
| * \param[in] src Pointer to the location of the data to be written. | * \param[in] src Pointer to the location of the data to be written. | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool writeBlock(uint32_t blockNumber, const uint8_t* src); | bool writeBlock(uint32_t blockNumber, const uint8_t* src); | ||||
| /** | /** | ||||
| * \param[in] block Logical block to be written. | * \param[in] block Logical block to be written. | ||||
| * \param[in] count Number of blocks to be written. | * \param[in] count Number of blocks to be written. | ||||
| * \param[in] src Pointer to the location of the data to be written. | * \param[in] src Pointer to the location of the data to be written. | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool writeBlocks(uint32_t block, const uint8_t* src, size_t count); | bool writeBlocks(uint32_t block, const uint8_t* src, size_t count); | ||||
| /** Write one data block in a multiple block write sequence | /** Write one data block in a multiple block write sequence | ||||
| * \param[in] src Pointer to the location of the data to be written. | * \param[in] src Pointer to the location of the data to be written. | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool writeData(const uint8_t* src); | bool writeData(const uint8_t* src); | ||||
| /** Start a write multiple blocks sequence. | /** Start a write multiple blocks sequence. | ||||
| * \note This function is used with writeData() and writeStop() | * \note This function is used with writeData() and writeStop() | ||||
| * for optimized multiple block writes. | * for optimized multiple block writes. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool writeStart(uint32_t blockNumber, uint32_t eraseCount); | bool writeStart(uint32_t blockNumber, uint32_t eraseCount); | ||||
| /** End a write multiple blocks sequence. | /** End a write multiple blocks sequence. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool writeStop(); | bool writeStop(); | ||||
| void chipSelectHigh(); | void chipSelectHigh(); | ||||
| void chipSelectLow(); | void chipSelectLow(); | ||||
| void spiYield(); | void spiYield(); | ||||
| void type(uint8_t value) {m_type = value;} | |||||
| void type(uint8_t value) { | |||||
| m_type = value; | |||||
| } | |||||
| bool waitNotBusy(uint16_t timeoutMillis); | bool waitNotBusy(uint16_t timeoutMillis); | ||||
| bool writeData(uint8_t token, const uint8_t* src); | bool writeData(uint8_t token, const uint8_t* src); | ||||
| void spiBegin() {m_spi->begin();} | |||||
| void spiInit(uint8_t spiDivisor) {m_spi->init(spiDivisor);} | |||||
| uint8_t spiReceive() {return m_spi->receive();} | |||||
| uint8_t spiReceive(uint8_t* buf, size_t n) {return m_spi->receive(buf, n);} | |||||
| void spiSend(uint8_t data) {m_spi->send(data);} | |||||
| void spiSend(const uint8_t* buf, size_t n) {m_spi->send(buf, n);} | |||||
| bool useSpiTransactions() {return m_spi->useSpiTransactions();} | |||||
| void spiBegin() { | |||||
| m_spi->begin(); | |||||
| } | |||||
| void spiInit(uint8_t spiDivisor) { | |||||
| m_spi->init(spiDivisor); | |||||
| } | |||||
| uint8_t spiReceive() { | |||||
| return m_spi->receive(); | |||||
| } | |||||
| uint8_t spiReceive(uint8_t* buf, size_t n) { | |||||
| return m_spi->receive(buf, n); | |||||
| } | |||||
| void spiSend(uint8_t data) { | |||||
| m_spi->send(data); | |||||
| } | |||||
| void spiSend(const uint8_t* buf, size_t n) { | |||||
| m_spi->send(buf, n); | |||||
| } | |||||
| bool useSpiTransactions() { | |||||
| return m_spi->useSpiTransactions(); | |||||
| } | |||||
| m_spi_t* m_spi; | m_spi_t* m_spi; | ||||
| uint8_t m_chipSelectPin; | uint8_t m_chipSelectPin; | ||||
| uint8_t m_errorCode; | uint8_t m_errorCode; | ||||
| * \param[in] chipSelectPin SD chip select pin. | * \param[in] chipSelectPin SD chip select pin. | ||||
| * \param[in] sckDivisor SPI clock divisor. | * \param[in] sckDivisor SPI clock divisor. | ||||
| * \return true for success else false. | * \return true for success else false. | ||||
| */ | |||||
| */ | |||||
| bool begin(uint8_t chipSelectPin = SS, uint8_t sckDivisor = 2) { | bool begin(uint8_t chipSelectPin = SS, uint8_t sckDivisor = 2) { | ||||
| return SdSpiCard::begin(&m_spi, chipSelectPin, sckDivisor); | return SdSpiCard::begin(&m_spi, chipSelectPin, sckDivisor); | ||||
| } | } | ||||
| * \param[in] chipSelectPin SD chip select pin. | * \param[in] chipSelectPin SD chip select pin. | ||||
| * \param[in] sckDivisor SPI clock divisor. | * \param[in] sckDivisor SPI clock divisor. | ||||
| * \return true for success else false. | * \return true for success else false. | ||||
| */ | |||||
| */ | |||||
| bool init(uint8_t sckDivisor = 2, uint8_t chipSelectPin = SS) { | bool init(uint8_t sckDivisor = 2, uint8_t chipSelectPin = SS) { | ||||
| return begin(chipSelectPin, sckDivisor); | return begin(chipSelectPin, sckDivisor); | ||||
| } | } | ||||
| private: | private: | ||||
| bool begin(m_spi_t* spi, uint8_t chipSelectPin = SS, | |||||
| uint8_t sckDivisor = SPI_FULL_SPEED) {return false;} | |||||
| bool begin(m_spi_t* spi, uint8_t chipSelectPin = SS, | |||||
| uint8_t sckDivisor = SPI_FULL_SPEED) { | |||||
| return false; | |||||
| } | |||||
| SpiDefault_t m_spi; | SpiDefault_t m_spi; | ||||
| }; | }; | ||||
| #endif // SpiCard_h | #endif // SpiCard_h |
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void SdSpi::begin() { | void SdSpi::begin() { | ||||
| PIO_Configure( | PIO_Configure( | ||||
| g_APinDescription[PIN_SPI_MOSI].pPort, | |||||
| g_APinDescription[PIN_SPI_MOSI].ulPinType, | |||||
| g_APinDescription[PIN_SPI_MOSI].ulPin, | |||||
| g_APinDescription[PIN_SPI_MOSI].ulPinConfiguration); | |||||
| g_APinDescription[PIN_SPI_MOSI].pPort, | |||||
| g_APinDescription[PIN_SPI_MOSI].ulPinType, | |||||
| g_APinDescription[PIN_SPI_MOSI].ulPin, | |||||
| g_APinDescription[PIN_SPI_MOSI].ulPinConfiguration); | |||||
| PIO_Configure( | PIO_Configure( | ||||
| g_APinDescription[PIN_SPI_MISO].pPort, | |||||
| g_APinDescription[PIN_SPI_MISO].ulPinType, | |||||
| g_APinDescription[PIN_SPI_MISO].ulPin, | |||||
| g_APinDescription[PIN_SPI_MISO].ulPinConfiguration); | |||||
| g_APinDescription[PIN_SPI_MISO].pPort, | |||||
| g_APinDescription[PIN_SPI_MISO].ulPinType, | |||||
| g_APinDescription[PIN_SPI_MISO].ulPin, | |||||
| g_APinDescription[PIN_SPI_MISO].ulPinConfiguration); | |||||
| PIO_Configure( | PIO_Configure( | ||||
| g_APinDescription[PIN_SPI_SCK].pPort, | |||||
| g_APinDescription[PIN_SPI_SCK].ulPinType, | |||||
| g_APinDescription[PIN_SPI_SCK].ulPin, | |||||
| g_APinDescription[PIN_SPI_SCK].ulPinConfiguration); | |||||
| g_APinDescription[PIN_SPI_SCK].pPort, | |||||
| g_APinDescription[PIN_SPI_SCK].ulPinType, | |||||
| g_APinDescription[PIN_SPI_SCK].ulPin, | |||||
| g_APinDescription[PIN_SPI_SCK].ulPinConfiguration); | |||||
| pmc_enable_periph_clk(ID_SPI0); | pmc_enable_periph_clk(ID_SPI0); | ||||
| #if USE_SAM3X_DMAC | #if USE_SAM3X_DMAC | ||||
| pmc_enable_periph_clk(ID_DMAC); | pmc_enable_periph_clk(ID_DMAC); | ||||
| DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_DADDR = (uint32_t)dst; | DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_DADDR = (uint32_t)dst; | ||||
| DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_DSCR = 0; | DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_DSCR = 0; | ||||
| DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CTRLA = count | | DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CTRLA = count | | ||||
| DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE; | |||||
| DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE; | |||||
| DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CTRLB = DMAC_CTRLB_SRC_DSCR | | DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CTRLB = DMAC_CTRLB_SRC_DSCR | | ||||
| DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_PER2MEM_DMA_FC | | |||||
| DMAC_CTRLB_SRC_INCR_FIXED | DMAC_CTRLB_DST_INCR_INCREMENTING; | |||||
| DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_PER2MEM_DMA_FC | | |||||
| DMAC_CTRLB_SRC_INCR_FIXED | DMAC_CTRLB_DST_INCR_INCREMENTING; | |||||
| DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CFG = DMAC_CFG_SRC_PER(SPI_RX_IDX) | | DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CFG = DMAC_CFG_SRC_PER(SPI_RX_IDX) | | ||||
| DMAC_CFG_SRC_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG; | |||||
| DMAC_CFG_SRC_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG; | |||||
| dmac_channel_enable(SPI_DMAC_RX_CH); | dmac_channel_enable(SPI_DMAC_RX_CH); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DADDR = (uint32_t)&SPI0->SPI_TDR; | DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DADDR = (uint32_t)&SPI0->SPI_TDR; | ||||
| DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DSCR = 0; | DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DSCR = 0; | ||||
| DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLA = count | | DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLA = count | | ||||
| DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE; | |||||
| DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE; | |||||
| DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLB = DMAC_CTRLB_SRC_DSCR | | DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLB = DMAC_CTRLB_SRC_DSCR | | ||||
| DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC | | |||||
| src_incr | DMAC_CTRLB_DST_INCR_FIXED; | |||||
| DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC | | |||||
| src_incr | DMAC_CTRLB_DST_INCR_FIXED; | |||||
| DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CFG = DMAC_CFG_DST_PER(SPI_TX_IDX) | | DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CFG = DMAC_CFG_DST_PER(SPI_TX_IDX) | | ||||
| DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ALAP_CFG; | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ALAP_CFG; | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| if (pSpi->SPI_SR & SPI_SR_OVRES) rtn |= 1; | |||||
| if (pSpi->SPI_SR & SPI_SR_OVRES) { | |||||
| rtn |= 1; | |||||
| } | |||||
| #else // USE_SAM3X_DMAC | #else // USE_SAM3X_DMAC | ||||
| for (size_t i = 0; i < n; i++) { | for (size_t i = 0; i < n; i++) { | ||||
| pSpi->SPI_TDR = 0XFF; | pSpi->SPI_TDR = 0XFF; |
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| * \class SdVolume | * \class SdVolume | ||||
| * \brief SdVolume used in Quick start. Soon to be removed. | |||||
| * \brief SdVolume Soon to be removed. | |||||
| */ | */ | ||||
| class SdVolume : public FatVolume { | class SdVolume : public FatVolume { | ||||
| public: | public: | ||||
| /** Initialize a FAT volume. Try partition one first then try super | |||||
| * floppy format. | |||||
| * | |||||
| * \param[in] dev The Sd2Card where the volume is located. | |||||
| * | |||||
| * \return true for success else false. | |||||
| */ | |||||
| bool init(Sd2Card* dev) {return init(dev, 1) ? true : init(dev, 0);} | |||||
| /** Initialize a FAT volume. | |||||
| * | |||||
| * \param[in] dev The Sd2Card where the volume is located. | |||||
| * \param[in] part the partition to use. Zero for super floppy or 1-4. | |||||
| * \return true for success else false. | |||||
| */ | |||||
| /** Initialize a FAT volume. Try partition one first then try super | |||||
| * floppy format. | |||||
| * | |||||
| * \param[in] dev The Sd2Card where the volume is located. | |||||
| * | |||||
| * \return true for success else false. | |||||
| */ | |||||
| bool init(Sd2Card* dev) { | |||||
| return init(dev, 1) ? true : init(dev, 0); | |||||
| } | |||||
| /** Initialize a FAT volume. | |||||
| * | |||||
| * \param[in] dev The Sd2Card where the volume is located. | |||||
| * \param[in] part the partition to use. Zero for super floppy or 1-4. | |||||
| * \return true for success else false. | |||||
| */ | |||||
| bool init(Sd2Card* dev, uint8_t part) { | bool init(Sd2Card* dev, uint8_t part) { | ||||
| m_sdCard = dev; | m_sdCard = dev; | ||||
| return FatVolume::init(part); | return FatVolume::init(part); |
| // A simple data logger for the Arduino analog pins with optional DS1307 | // A simple data logger for the Arduino analog pins with optional DS1307 | ||||
| // uses RTClib from https://github.com/adafruit/RTClib | // uses RTClib from https://github.com/adafruit/RTClib | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| #include <SdFatUtil.h> // define FreeRam() | #include <SdFatUtil.h> // define FreeRam() | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // call back for file timestamps | // call back for file timestamps | ||||
| void dateTime(uint16_t* date, uint16_t* time) { | void dateTime(uint16_t* date, uint16_t* time) { | ||||
| DateTime now = RTC.now(); | |||||
| DateTime now = RTC.now(); | |||||
| // return date using FAT_DATE macro to format fields | // return date using FAT_DATE macro to format fields | ||||
| *date = FAT_DATE(now.year(), now.month(), now.day()); | *date = FAT_DATE(now.year(), now.month(), now.day()); | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void setup() { | void setup() { | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial){} // wait for Leonardo | |||||
| // pstr stores strings in flash to save RAM | |||||
| cout << endl << pstr("FreeRam: ") << FreeRam() << endl; | |||||
| while (!Serial) {} // wait for Leonardo | |||||
| // F() stores strings in flash to save RAM | |||||
| cout << endl << F("FreeRam: ") << FreeRam() << endl; | |||||
| #if WAIT_TO_START | #if WAIT_TO_START | ||||
| cout << pstr("Type any character to start\n"); | |||||
| cout << F("Type any character to start\n"); | |||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| while (Serial.read() >= 0) {} | |||||
| #endif // WAIT_TO_START | #endif // WAIT_TO_START | ||||
| #if USE_DS1307 | #if USE_DS1307 | ||||
| // connect to RTC | // connect to RTC | ||||
| Wire.begin(); | Wire.begin(); | ||||
| if (!RTC.begin()) error("RTC failed"); | |||||
| if (!RTC.begin()) { | |||||
| error("RTC failed"); | |||||
| } | |||||
| // set date time callback function | // set date time callback function | ||||
| SdFile::dateTimeCallback(dateTime); | SdFile::dateTimeCallback(dateTime); | ||||
| #endif // USE_DS1307 | #endif // USE_DS1307 | ||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| if (!sd.begin(SD_CHIP_SELECT, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(SD_CHIP_SELECT, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // create a new file in root, the current working directory | // create a new file in root, the current working directory | ||||
| char name[] = "LOGGER00.CSV"; | |||||
| char name[] = "logger00.csv"; | |||||
| for (uint8_t i = 0; i < 100; i++) { | for (uint8_t i = 0; i < 100; i++) { | ||||
| name[6] = i/10 + '0'; | name[6] = i/10 + '0'; | ||||
| name[7] = i%10 + '0'; | name[7] = i%10 + '0'; | ||||
| if (sd.exists(name)) continue; | |||||
| if (sd.exists(name)) { | |||||
| continue; | |||||
| } | |||||
| logfile.open(name); | logfile.open(name); | ||||
| break; | break; | ||||
| } | } | ||||
| if (!logfile.is_open()) error("file.open"); | |||||
| if (!logfile.is_open()) { | |||||
| error("file.open"); | |||||
| } | |||||
| cout << F("Logging to: ") << name << endl; | |||||
| cout << F("Type any character to stop\n\n"); | |||||
| cout << pstr("Logging to: ") << name << endl; | |||||
| cout << pstr("Type any character to stop\n\n"); | |||||
| // format header in buffer | // format header in buffer | ||||
| obufstream bout(buf, sizeof(buf)); | obufstream bout(buf, sizeof(buf)); | ||||
| bout << pstr("millis"); | |||||
| bout << F("millis"); | |||||
| #if USE_DS1307 | #if USE_DS1307 | ||||
| bout << pstr(",date,time"); | |||||
| bout << F(",date,time"); | |||||
| #endif // USE_DS1307 | #endif // USE_DS1307 | ||||
| for (uint8_t i = 0; i < SENSOR_COUNT; i++) { | for (uint8_t i = 0; i < SENSOR_COUNT; i++) { | ||||
| bout << pstr(",sens") << int(i); | |||||
| bout << F(",sens") << int(i); | |||||
| } | } | ||||
| logfile << buf << endl; | logfile << buf << endl; | ||||
| logfile << buf << flush; | logfile << buf << flush; | ||||
| // check for error | // check for error | ||||
| if (!logfile) error("write data failed"); | |||||
| if (!logfile) { | |||||
| error("write data failed"); | |||||
| } | |||||
| #if ECHO_TO_SERIAL | #if ECHO_TO_SERIAL | ||||
| cout << buf; | cout << buf; | ||||
| #endif // ECHO_TO_SERIAL | #endif // ECHO_TO_SERIAL | ||||
| // don't log two points in the same millis | // don't log two points in the same millis | ||||
| if (m == millis()) delay(1); | |||||
| if (!Serial.available()) return; | |||||
| if (m == millis()) { | |||||
| delay(1); | |||||
| } | |||||
| if (!Serial.available()) { | |||||
| return; | |||||
| } | |||||
| logfile.close(); | logfile.close(); | ||||
| cout << pstr("Done!"); | |||||
| cout << F("Done!"); | |||||
| while (1); | while (1); | ||||
| } | |||||
| } |
| /* | |||||
| * Program to test Short File Name character case flags. | |||||
| */ | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | |||||
| SdFat sd; | |||||
| SdFile file; | |||||
| char* name[] = { | |||||
| "low.low", "low.Mix", "low.UP", | |||||
| "Mix.low", "Mix.Mix", "Mix.UP", | |||||
| "UP.low", "UP.Mix", "UP.UP" | |||||
| }; | |||||
| //------------------------------------------------------------------------------ | |||||
| void setup() { | |||||
| Serial.begin(9600); | |||||
| while (!Serial) {} // wait for Leonardo | |||||
| Serial.println("type any character to start"); | |||||
| while (Serial.read() < 0) {} | |||||
| if (!sd.begin()) { | |||||
| Serial.println("begin failed"); | |||||
| return; | |||||
| } | |||||
| for (uint8_t i = 0; i < 9; i++) { | |||||
| sd.remove(name[i]); | |||||
| if (!file.open(name[i], O_RDWR | O_CREAT | O_EXCL)) { | |||||
| sd.errorHalt(name[i]); | |||||
| } | |||||
| file.println(name[i]); | |||||
| file.close(); | |||||
| } | |||||
| sd.ls(LS_DATE|LS_SIZE); | |||||
| Serial.println("Done"); | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| void loop() {} |
| void setup() { | void setup() { | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| delay(2000); | delay(2000); | ||||
| /* | /* | ||||
| * This sketch is a simple Print benchmark. | |||||
| * This program is a simple Print benchmark. | |||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SD.h> | #include <SD.h> | ||||
| // SD chip select pin | // SD chip select pin | ||||
| while (Serial.read() >= 0) { | while (Serial.read() >= 0) { | ||||
| } | } | ||||
| // pstr stores strings in flash to save RAM | |||||
| // F() stores strings in flash to save RAM | |||||
| Serial.println(F("Type any character to start")); | Serial.println(F("Type any character to start")); | ||||
| while (Serial.read() <= 0) { | while (Serial.read() <= 0) { | ||||
| } | } | ||||
| // initialize the SD card | // initialize the SD card | ||||
| if (!SD.begin(chipSelect)) error("begin"); | |||||
| if (!SD.begin(chipSelect)) { | |||||
| error("begin"); | |||||
| } | |||||
| Serial.println(F("Starting print test. Please wait.\n")); | Serial.println(F("Starting print test. Please wait.\n")); | ||||
| // do write test | // do write test | ||||
| for (int test = 0; test < 2; test++) { | for (int test = 0; test < 2; test++) { | ||||
| file = SD.open("BENCH.TXT", FILE_WRITE); | |||||
| if (!file) error("open failed"); | |||||
| file = SD.open("bench.txt", FILE_WRITE); | |||||
| if (!file) { | |||||
| error("open failed"); | |||||
| } | |||||
| switch(test) { | switch(test) { | ||||
| case 0: | case 0: | ||||
| Serial.println(F("Test of println(uint16_t)")); | Serial.println(F("Test of println(uint16_t)")); | ||||
| error("write failed"); | error("write failed"); | ||||
| } | } | ||||
| m = micros() - m; | m = micros() - m; | ||||
| if (maxLatency < m) maxLatency = m; | |||||
| if (minLatency > m) minLatency = m; | |||||
| if (maxLatency < m) { | |||||
| maxLatency = m; | |||||
| } | |||||
| if (minLatency > m) { | |||||
| minLatency = m; | |||||
| } | |||||
| totalLatency += m; | totalLatency += m; | ||||
| } | } | ||||
| file.flush(); | file.flush(); | ||||
| Serial.print(F(" usec, Avg Latency: ")); | Serial.print(F(" usec, Avg Latency: ")); | ||||
| Serial.print(totalLatency/N_PRINT); | Serial.print(totalLatency/N_PRINT); | ||||
| Serial.println(F(" usec\n")); | Serial.println(F(" usec\n")); | ||||
| SD.remove("BENCH.TXT"); | |||||
| SD.remove("bench.txt"); | |||||
| } | } | ||||
| file.close(); | file.close(); | ||||
| Serial.println(F("Done!\n")); | Serial.println(F("Done!\n")); |
| /* | /* | ||||
| * Sketch to compare size of Arduino SD library with SdFat V2. | |||||
| * See SdFatSize.pde for SdFat sketch. | |||||
| * Program to compare size of Arduino SD library with SdFat. | |||||
| * See SdFatSize.ino for SdFat program. | |||||
| */ | */ | ||||
| #include <SPI.h> | #include <SPI.h> | ||||
| #include <SD.h> | #include <SD.h> |
| /* | /* | ||||
| * Sketch to compare size of SdFat V2 with Arduino SD library. | |||||
| * See SD_Size.pde for Arduino SD sketch. | |||||
| * Program to compare size of SdFat with Arduino SD library. | |||||
| * See SD_Size.ino for Arduino SD program. | |||||
| * | * | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| SdFat sd; | SdFat sd; | ||||
| Serial.println("begin failed"); | Serial.println("begin failed"); | ||||
| return; | return; | ||||
| } | } | ||||
| file.open("SIZE_TST.TXT", O_RDWR | O_CREAT | O_AT_END); | |||||
| file.open("SizeTest.txt", O_RDWR | O_CREAT | O_AT_END); | |||||
| file.println("Hello"); | file.println("Hello"); | ||||
| /* | /* | ||||
| * Append Example | * Append Example | ||||
| * | * | ||||
| * This sketch shows how to use open for append. | |||||
| * The sketch will append 100 line each time it opens the file. | |||||
| * The sketch will open and close the file 100 times. | |||||
| * This program shows how to use open for append. | |||||
| * The program will append 100 line each time it opens the file. | |||||
| * The program will open and close the file 100 times. | |||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // SD chip select pin | // SD chip select pin | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void setup() { | void setup() { | ||||
| // filename for this example | // filename for this example | ||||
| char name[] = "APPEND.TXT"; | |||||
| char name[] = "append.txt"; | |||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| // pstr() stores strings in flash to save RAM | |||||
| cout << endl << pstr("Type any character to start\n"); | |||||
| // F() stores strings in flash to save RAM | |||||
| cout << endl << F("Type any character to start\n"); | |||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // Catch Due reset problem | delay(400); // Catch Due reset problem | ||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| cout << F("Appending to: ") << name; | |||||
| cout << pstr("Appending to: ") << name; | |||||
| for (uint8_t i = 0; i < 100; i++) { | for (uint8_t i = 0; i < 100; i++) { | ||||
| // open stream for append | // open stream for append | ||||
| ofstream sdout(name, ios::out | ios::app); | ofstream sdout(name, ios::out | ios::app); | ||||
| if (!sdout) error("open failed"); | |||||
| if (!sdout) { | |||||
| error("open failed"); | |||||
| } | |||||
| // append 100 lines to the file | // append 100 lines to the file | ||||
| for (uint8_t j = 0; j < 100; j++) { | for (uint8_t j = 0; j < 100; j++) { | ||||
| // close the stream | // close the stream | ||||
| sdout.close(); | sdout.close(); | ||||
| if (!sdout) error("append data failed"); | |||||
| if (!sdout) { | |||||
| error("append data failed"); | |||||
| } | |||||
| // output progress indicator | // output progress indicator | ||||
| if (i % 25 == 0) cout << endl; | |||||
| if (i % 25 == 0) { | |||||
| cout << endl; | |||||
| } | |||||
| cout << '.'; | cout << '.'; | ||||
| } | } | ||||
| cout << endl << "Done" << endl; | cout << endl << "Done" << endl; |
| /* | /* | ||||
| * Calculate the sum and average of a list of floating point numbers | |||||
| * Calculate the sum and average of a list of floating point numbers | |||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // SD chip select pin | // SD chip select pin | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void writeTestFile() { | void writeTestFile() { | ||||
| // open the output file | // open the output file | ||||
| ofstream sdout("AVG_TEST.TXT"); | |||||
| ofstream sdout("AvgTest.txt"); | |||||
| // write a series of float numbers | // write a series of float numbers | ||||
| for (int16_t i = -1001; i < 2000; i += 13) { | for (int16_t i = -1001; i < 2000; i += 13) { | ||||
| sdout << 0.1 * i << endl; | sdout << 0.1 * i << endl; | ||||
| } | } | ||||
| if (!sdout) sd.errorHalt("sdout failed"); | |||||
| if (!sdout) { | |||||
| sd.errorHalt("sdout failed"); | |||||
| } | |||||
| sdout.close(); | sdout.close(); | ||||
| } | } | ||||
| double sum = 0; // sum of input numbers | double sum = 0; // sum of input numbers | ||||
| // open the input file | // open the input file | ||||
| ifstream sdin("AVG_TEST.TXT"); | |||||
| ifstream sdin("AvgTest.txt"); | |||||
| // check for an open failure | // check for an open failure | ||||
| if (!sdin) sd.errorHalt("sdin failed"); | |||||
| if (!sdin) { | |||||
| sd.errorHalt("sdin failed"); | |||||
| } | |||||
| // read and sum the numbers | // read and sum the numbers | ||||
| while (sdin >> num) { | while (sdin >> num) { | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| // pstr stores strings in flash to save RAM | |||||
| cout << pstr("Type any character to start\n"); | |||||
| // F() stores strings in flash to save RAM | |||||
| cout << F("Type any character to start\n"); | |||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // Catch Due reset problem | delay(400); // Catch Due reset problem | ||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // write the test file | // write the test file | ||||
| writeTestFile(); | writeTestFile(); |
| /* | /* | ||||
| * This sketch is a simple binary write/read benchmark | |||||
| * This program is a simple binary write/read benchmark | |||||
| * for the standard Arduino SD.h library. | * for the standard Arduino SD.h library. | ||||
| */ | */ | ||||
| #include <SPI.h> | #include <SPI.h> | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void setup() { | void setup() { | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial){} // wait for Leonardo | |||||
| while (!Serial) {} // wait for Leonardo | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void loop() { | void loop() { | ||||
| // discard any input | // discard any input | ||||
| while (Serial.read() >= 0) {} | while (Serial.read() >= 0) {} | ||||
| // pstr stores strings in flash to save RAM | |||||
| // F() stores strings in flash to save RAM | |||||
| Serial.println(F("Type any character to start")); | Serial.println(F("Type any character to start")); | ||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| if (!SD.begin(chipSelect)) error("begin"); | |||||
| if (!SD.begin(chipSelect)) { | |||||
| error("begin"); | |||||
| } | |||||
| // open or create file - truncate existing file. | // open or create file - truncate existing file. | ||||
| file = SD.open("BENCH.DAT", FILE_WRITE | O_TRUNC); | |||||
| // file = SD.open("BENCH.DAT", O_CREAT | O_TRUNC | O_CREAT); | |||||
| file = SD.open("Bench.dat", O_CREAT | O_TRUNC | O_CREAT); | |||||
| if (!file) { | if (!file) { | ||||
| error("open failed"); | error("open failed"); | ||||
| } | } | ||||
| error("write failed"); | error("write failed"); | ||||
| } | } | ||||
| m = micros() - m; | m = micros() - m; | ||||
| if (maxLatency < m) maxLatency = m; | |||||
| if (minLatency > m) minLatency = m; | |||||
| if (maxLatency < m) { | |||||
| maxLatency = m; | |||||
| } | |||||
| if (minLatency > m) { | |||||
| minLatency = m; | |||||
| } | |||||
| totalLatency += m; | totalLatency += m; | ||||
| } | } | ||||
| file.flush(); | file.flush(); | ||||
| error("read failed"); | error("read failed"); | ||||
| } | } | ||||
| m = micros() - m; | m = micros() - m; | ||||
| if (maxLatency < m) maxLatency = m; | |||||
| if (minLatency > m) minLatency = m; | |||||
| if (maxLatency < m) { | |||||
| maxLatency = m; | |||||
| } | |||||
| if (minLatency > m) { | |||||
| minLatency = m; | |||||
| } | |||||
| totalLatency += m; | totalLatency += m; | ||||
| if (buf[BUF_SIZE-1] != '\n') { | if (buf[BUF_SIZE-1] != '\n') { | ||||
| error("data check"); | error("data check"); |
| /* | /* | ||||
| * Use of ibufsteam to parse a line and obufstream to format a line | * Use of ibufsteam to parse a line and obufstream to format a line | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // create a serial output stream | // create a serial output stream | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| delay(2000); | delay(2000); | ||||
| // initialize input string | // initialize input string | ||||
| ibufstream bin("123 456 789"); | ibufstream bin("123 456 789"); | ||||
| /* | /* | ||||
| * Append a line to a file - demo of pathnames and streams | * Append a line to a file - demo of pathnames and streams | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // SD chip select pin | // SD chip select pin | ||||
| ArduinoOutStream cout(Serial); | ArduinoOutStream cout(Serial); | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /* | /* | ||||
| * Append a line to LOGFILE.TXT | |||||
| * Append a line to logfile.txt | |||||
| */ | */ | ||||
| void logEvent(const char *msg) { | void logEvent(const char *msg) { | ||||
| // create dir if needed | // create dir if needed | ||||
| sd.mkdir("LOGS/2011/JAN"); | |||||
| sd.mkdir("logs/2014/Jan"); | |||||
| // create or open a file for append | // create or open a file for append | ||||
| ofstream sdlog("LOGS/2011/JAN/LOGFILE.TXT", ios::out | ios::app); | |||||
| ofstream sdlog("logs/2014/Jan/logfile.txt", ios::out | ios::app); | |||||
| // append a line to the file | // append a line to the file | ||||
| sdlog << msg << endl; | sdlog << msg << endl; | ||||
| // check for errors | // check for errors | ||||
| if (!sdlog) sd.errorHalt("append failed"); | |||||
| if (!sdlog) { | |||||
| sd.errorHalt("append failed"); | |||||
| } | |||||
| sdlog.close(); | sdlog.close(); | ||||
| } | } | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| // pstr stores strings in flash to save RAM | |||||
| cout << pstr("Type any character to start\n"); | |||||
| // F() stores strings in flash to save RAM | |||||
| cout << F("Type any character to start\n"); | |||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // append a line to the logfile | // append a line to the logfile | ||||
| logEvent("Another line for the logfile"); | logEvent("Another line for the logfile"); | ||||
| cout << "Done - check /LOGS/2011/JAN/LOGFILE.TXT on the SD" << endl; | |||||
| cout << F("Done - check /logs/2014/Jan/logfile.txt on the SD") << endl; | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void loop() {} | void loop() {} |
| // Demo of rewriting a line read by fgets | // Demo of rewriting a line read by fgets | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // SD card chip select pin | // SD card chip select pin | ||||
| char line[25]; | char line[25]; | ||||
| int c; | int c; | ||||
| uint32_t pos; | uint32_t pos; | ||||
| // open test file | // open test file | ||||
| SdFile rdfile("FGETS.TXT", O_RDWR); | |||||
| SdFile rdfile("fgets.txt", O_RDWR); | |||||
| // check for open error | // check for open error | ||||
| if (!rdfile.isOpen()) error("demoFgets"); | |||||
| if (!rdfile.isOpen()) { | |||||
| error("demoFgets"); | |||||
| } | |||||
| // list file | // list file | ||||
| cout << pstr("-----Before Rewrite\r\n"); | |||||
| while ((c = rdfile.read()) >= 0) Serial.write(c); | |||||
| rdfile.rewind(); | |||||
| cout << F("-----Before Rewrite\r\n"); | |||||
| while ((c = rdfile.read()) >= 0) { | |||||
| Serial.write(c); | |||||
| } | |||||
| rdfile.rewind(); | |||||
| // read lines from the file to get position | // read lines from the file to get position | ||||
| while (1) { | while (1) { | ||||
| pos = rdfile.curPosition(); | pos = rdfile.curPosition(); | ||||
| error("Line not found"); | error("Line not found"); | ||||
| } | } | ||||
| // find line that contains "Line C" | // find line that contains "Line C" | ||||
| if (strstr(line, "Line C"))break; | |||||
| if (strstr(line, "Line C")) { | |||||
| break; | |||||
| } | |||||
| } | |||||
| // rewrite line with 'C' | |||||
| if (!rdfile.seekSet(pos)) { | |||||
| error("seekSet"); | |||||
| } | } | ||||
| // rewrite line with 'C' | |||||
| if (!rdfile.seekSet(pos))error("seekSet"); | |||||
| rdfile.println("Line R"); | rdfile.println("Line R"); | ||||
| rdfile.rewind(); | rdfile.rewind(); | ||||
| // list file | // list file | ||||
| cout << pstr("\r\n-----After Rewrite\r\n"); | |||||
| while ((c = rdfile.read()) >= 0) Serial.write(c); | |||||
| cout << F("\r\n-----After Rewrite\r\n"); | |||||
| while ((c = rdfile.read()) >= 0) { | |||||
| Serial.write(c); | |||||
| } | |||||
| // close so rewrite is not lost | // close so rewrite is not lost | ||||
| rdfile.close(); | rdfile.close(); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void makeTestFile() { | void makeTestFile() { | ||||
| // create or open test file | // create or open test file | ||||
| SdFile wrfile("FGETS.TXT", O_WRITE | O_CREAT | O_TRUNC); | |||||
| SdFile wrfile("fgets.txt", O_WRITE | O_CREAT | O_TRUNC); | |||||
| // check for open error | // check for open error | ||||
| if (!wrfile.isOpen()) error("MakeTestFile"); | |||||
| if (!wrfile.isOpen()) { | |||||
| error("MakeTestFile"); | |||||
| } | |||||
| // write test file | // write test file | ||||
| wrfile.print(F( | wrfile.print(F( | ||||
| "Line A\r\n" | |||||
| "Line B\r\n" | |||||
| "Line C\r\n" | |||||
| "Line D\r\n" | |||||
| "Line E\r\n" | |||||
| )); | |||||
| "Line A\r\n" | |||||
| "Line B\r\n" | |||||
| "Line C\r\n" | |||||
| "Line D\r\n" | |||||
| "Line E\r\n" | |||||
| )); | |||||
| wrfile.close(); | wrfile.close(); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void setup(void) { | void setup(void) { | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial){} // wait for Leonardo | |||||
| while (!Serial) {} // wait for Leonardo | |||||
| cout << pstr("Type any character to start\n"); | |||||
| cout << F("Type any character to start\n"); | |||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| makeTestFile(); | makeTestFile(); | ||||
| demoFgets(); | demoFgets(); | ||||
| cout << pstr("\nDone\n"); | |||||
| cout << F("\nDone\n"); | |||||
| } | } | ||||
| void loop(void) {} | void loop(void) {} |
| /* | /* | ||||
| * Read the logfile created by the eventlog.pde example. | |||||
| * Read the logfile created by the eventlog.ino example. | |||||
| * Demo of pathnames and working directories | * Demo of pathnames and working directories | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // SD chip select pin | // SD chip select pin | ||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // set current working directory | // set current working directory | ||||
| if (!sd.chdir("LOGS/2011/JAN/")) { | |||||
| sd.errorHalt("chdir failed. Did you run eventlog.pde?"); | |||||
| if (!sd.chdir("logs/2014/Jan/")) { | |||||
| sd.errorHalt("chdir failed. Did you run eventlog.ino?"); | |||||
| } | } | ||||
| // open file in current working directory | // open file in current working directory | ||||
| ifstream file("LOGFILE.TXT"); | |||||
| ifstream file("logfile.txt"); | |||||
| if (!file.is_open()) sd.errorHalt("open failed"); | |||||
| if (!file.is_open()) { | |||||
| sd.errorHalt("open failed"); | |||||
| } | |||||
| // copy the file to Serial | // copy the file to Serial | ||||
| while ((c = file.get()) >= 0) cout << (char)c; | |||||
| while ((c = file.get()) >= 0) { | |||||
| cout << (char)c; | |||||
| } | |||||
| cout << "Done" << endl; | cout << "Done" << endl; | ||||
| } | } |
| * Samples are logged at regular intervals. Each Sample consists of the ADC | * Samples are logged at regular intervals. Each Sample consists of the ADC | ||||
| * values for the analog pins defined in the PIN_LIST array. The pins numbers | * values for the analog pins defined in the PIN_LIST array. The pins numbers | ||||
| * may be in any order. | * may be in any order. | ||||
| * | |||||
| * | |||||
| * Edit the configuration constants below to set the sample pins, sample rate, | * Edit the configuration constants below to set the sample pins, sample rate, | ||||
| * and other configuration values. | * and other configuration values. | ||||
| * | * | ||||
| * Data is written to the file using a SD multiple block write command. | * Data is written to the file using a SD multiple block write command. | ||||
| */ | */ | ||||
| #ifdef __AVR__ | #ifdef __AVR__ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| #include <SdFatUtil.h> | #include <SdFatUtil.h> | ||||
| #include "AnalogBinLogger.h" | #include "AnalogBinLogger.h" | ||||
| // | // | ||||
| // You can select an ADC clock rate by defining the symbol ADC_PRESCALER to | // You can select an ADC clock rate by defining the symbol ADC_PRESCALER to | ||||
| // one of these values. You must choose an appropriate ADC clock rate for | // one of these values. You must choose an appropriate ADC clock rate for | ||||
| // your sample interval. | |||||
| // your sample interval. | |||||
| // #define ADC_PRESCALER 7 // F_CPU/128 125 kHz on an Uno | // #define ADC_PRESCALER 7 // F_CPU/128 125 kHz on an Uno | ||||
| // #define ADC_PRESCALER 6 // F_CPU/64 250 kHz on an Uno | // #define ADC_PRESCALER 6 // F_CPU/64 250 kHz on an Uno | ||||
| // #define ADC_PRESCALER 5 // F_CPU/32 500 kHz on an Uno | // #define ADC_PRESCALER 5 // F_CPU/32 500 kHz on an Uno | ||||
| const uint32_t FILE_BLOCK_COUNT = 256000; | const uint32_t FILE_BLOCK_COUNT = 256000; | ||||
| // log file base name. Must be six characters or less. | // log file base name. Must be six characters or less. | ||||
| #define FILE_BASE_NAME "ANALOG" | |||||
| #define FILE_BASE_NAME "analog" | |||||
| // Set RECORD_EIGHT_BITS non-zero to record only the high 8-bits of the ADC. | // Set RECORD_EIGHT_BITS non-zero to record only the high 8-bits of the ADC. | ||||
| #define RECORD_EIGHT_BITS 0 | #define RECORD_EIGHT_BITS 0 | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // Buffer definitions. | // Buffer definitions. | ||||
| // | // | ||||
| // The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT additional | |||||
| // The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT additional | |||||
| // buffers. QUEUE_DIM must be a power of two larger than | // buffers. QUEUE_DIM must be a power of two larger than | ||||
| //(BUFFER_BLOCK_COUNT + 1). | //(BUFFER_BLOCK_COUNT + 1). | ||||
| // | // | ||||
| // End of configuration constants. | // End of configuration constants. | ||||
| //============================================================================== | //============================================================================== | ||||
| // Temporary log file. Will be deleted if a reset or power failure occurs. | // Temporary log file. Will be deleted if a reset or power failure occurs. | ||||
| #define TMP_FILE_NAME "TMP_LOG.BIN" | |||||
| #define TMP_FILE_NAME "tmp_log.bin" | |||||
| // Size of file base name. Must not be larger than six. | // Size of file base name. Must not be larger than six. | ||||
| const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1; | const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1; | ||||
| SdBaseFile binFile; | SdBaseFile binFile; | ||||
| char binName[13] = FILE_BASE_NAME "00.BIN"; | |||||
| char binName[13] = FILE_BASE_NAME "00.bin"; | |||||
| #if RECORD_EIGHT_BITS | #if RECORD_EIGHT_BITS | ||||
| const size_t SAMPLES_PER_BLOCK = DATA_DIM8/PIN_COUNT; | const size_t SAMPLES_PER_BLOCK = DATA_DIM8/PIN_COUNT; | ||||
| uint8_t fullTail; | uint8_t fullTail; | ||||
| // queueNext assumes QUEUE_DIM is a power of two | // queueNext assumes QUEUE_DIM is a power of two | ||||
| inline uint8_t queueNext(uint8_t ht) {return (ht + 1) & (QUEUE_DIM -1);} | |||||
| inline uint8_t queueNext(uint8_t ht) { | |||||
| return (ht + 1) & (QUEUE_DIM -1); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| // Interrupt Service Routines | // Interrupt Service Routines | ||||
| #if RECORD_EIGHT_BITS | #if RECORD_EIGHT_BITS | ||||
| uint8_t d = ADCH; | uint8_t d = ADCH; | ||||
| #else // RECORD_EIGHT_BITS | #else // RECORD_EIGHT_BITS | ||||
| // This will access ADCL first. | |||||
| // This will access ADCL first. | |||||
| uint16_t d = ADC; | uint16_t d = ADC; | ||||
| #endif // RECORD_EIGHT_BITS | #endif // RECORD_EIGHT_BITS | ||||
| if (isrBufNeeded && emptyHead == emptyTail) { | if (isrBufNeeded && emptyHead == emptyTail) { | ||||
| // no buffers - count overrun | // no buffers - count overrun | ||||
| if (isrOver < 0XFFFF) isrOver++; | |||||
| if (isrOver < 0XFFFF) { | |||||
| isrOver++; | |||||
| } | |||||
| // Avoid missed timer error. | // Avoid missed timer error. | ||||
| timerFlag = false; | timerFlag = false; | ||||
| return; | return; | ||||
| ADMUX = adcmux[adcindex]; | ADMUX = adcmux[adcindex]; | ||||
| ADCSRB = adcsrb[adcindex]; | ADCSRB = adcsrb[adcindex]; | ||||
| ADCSRA = adcsra[adcindex]; | ADCSRA = adcsra[adcindex]; | ||||
| if (adcindex == 0) timerFlag = false; | |||||
| if (adcindex == 0) { | |||||
| timerFlag = false; | |||||
| } | |||||
| adcindex = adcindex < (PIN_COUNT - 1) ? adcindex + 1 : 0; | adcindex = adcindex < (PIN_COUNT - 1) ? adcindex + 1 : 0; | ||||
| } else { | } else { | ||||
| timerFlag = false; | timerFlag = false; | ||||
| } | } | ||||
| // Check for buffer needed. | // Check for buffer needed. | ||||
| if (isrBufNeeded) { | |||||
| if (isrBufNeeded) { | |||||
| // Remove buffer from empty queue. | // Remove buffer from empty queue. | ||||
| isrBuf = emptyQueue[emptyTail]; | isrBuf = emptyQueue[emptyTail]; | ||||
| emptyTail = queueNext(emptyTail); | emptyTail = queueNext(emptyTail); | ||||
| isrBuf->count = 0; | isrBuf->count = 0; | ||||
| isrBuf->overrun = isrOver; | isrBuf->overrun = isrOver; | ||||
| isrBufNeeded = false; | |||||
| isrBufNeeded = false; | |||||
| } | } | ||||
| // Store ADC data. | // Store ADC data. | ||||
| isrBuf->data[isrBuf->count++] = d; | isrBuf->data[isrBuf->count++] = d; | ||||
| // Check for buffer full. | // Check for buffer full. | ||||
| if (isrBuf->count >= PIN_COUNT*SAMPLES_PER_BLOCK) { | if (isrBuf->count >= PIN_COUNT*SAMPLES_PER_BLOCK) { | ||||
| // Put buffer isrIn full queue. | |||||
| // Put buffer isrIn full queue. | |||||
| uint8_t tmp = fullHead; // Avoid extra fetch of volatile fullHead. | uint8_t tmp = fullHead; // Avoid extra fetch of volatile fullHead. | ||||
| fullQueue[tmp] = (block_t*)isrBuf; | fullQueue[tmp] = (block_t*)isrBuf; | ||||
| fullHead = queueNext(tmp); | fullHead = queueNext(tmp); | ||||
| // Set buffer needed and clear overruns. | // Set buffer needed and clear overruns. | ||||
| isrBufNeeded = true; | isrBufNeeded = true; | ||||
| isrOver = 0; | isrOver = 0; | ||||
| // timer1 interrupt to clear OCF1B | // timer1 interrupt to clear OCF1B | ||||
| ISR(TIMER1_COMPB_vect) { | ISR(TIMER1_COMPB_vect) { | ||||
| // Make sure ADC ISR responded to timer event. | // Make sure ADC ISR responded to timer event. | ||||
| if (timerFlag) timerError = true; | |||||
| if (timerFlag) { | |||||
| timerError = true; | |||||
| } | |||||
| timerFlag = true; | timerFlag = true; | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // initialize ADC and timer1 | // initialize ADC and timer1 | ||||
| void adcInit(metadata_t* meta) { | void adcInit(metadata_t* meta) { | ||||
| uint8_t adps; // prescaler bits for ADCSRA | |||||
| uint8_t adps; // prescaler bits for ADCSRA | |||||
| uint32_t ticks = F_CPU*SAMPLE_INTERVAL + 0.5; // Sample interval cpu cycles. | uint32_t ticks = F_CPU*SAMPLE_INTERVAL + 0.5; // Sample interval cpu cycles. | ||||
| if (ADC_REF & ~((1 << REFS0) | (1 << REFS1))) { | if (ADC_REF & ~((1 << REFS0) | (1 << REFS1))) { | ||||
| #else // ADC_PRESCALER | #else // ADC_PRESCALER | ||||
| // Allow extra cpu cycles to change ADC settings if more than one pin. | // Allow extra cpu cycles to change ADC settings if more than one pin. | ||||
| int32_t adcCycles = (ticks - ISR_TIMER0)/PIN_COUNT; | int32_t adcCycles = (ticks - ISR_TIMER0)/PIN_COUNT; | ||||
| - (PIN_COUNT > 1 ? ISR_SETUP_ADC : 0); | |||||
| - (PIN_COUNT > 1 ? ISR_SETUP_ADC : 0); | |||||
| for (adps = 7; adps > 0; adps--) { | for (adps = 7; adps > 0; adps--) { | ||||
| if (adcCycles >= (MIN_ADC_CYCLES << adps)) break; | |||||
| if (adcCycles >= (MIN_ADC_CYCLES << adps)) { | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| #endif // ADC_PRESCALER | #endif // ADC_PRESCALER | ||||
| meta->adcFrequency = F_CPU >> adps; | meta->adcFrequency = F_CPU >> adps; | ||||
| } | } | ||||
| meta->pinCount = PIN_COUNT; | meta->pinCount = PIN_COUNT; | ||||
| meta->recordEightBits = RECORD_EIGHT_BITS; | meta->recordEightBits = RECORD_EIGHT_BITS; | ||||
| for (int i = 0; i < PIN_COUNT; i++) { | for (int i = 0; i < PIN_COUNT; i++) { | ||||
| uint8_t pin = PIN_LIST[i]; | uint8_t pin = PIN_LIST[i]; | ||||
| if (pin >= NUM_ANALOG_INPUTS) error("Invalid Analog pin number"); | |||||
| if (pin >= NUM_ANALOG_INPUTS) { | |||||
| error("Invalid Analog pin number"); | |||||
| } | |||||
| meta->pinNumber[i] = pin; | meta->pinNumber[i] = pin; | ||||
| // Set ADC reference and low three bits of analog pin number. | |||||
| // Set ADC reference and low three bits of analog pin number. | |||||
| adcmux[i] = (pin & 7) | ADC_REF; | adcmux[i] = (pin & 7) | ADC_REF; | ||||
| if (RECORD_EIGHT_BITS) adcmux[i] |= 1 << ADLAR; | |||||
| if (RECORD_EIGHT_BITS) { | |||||
| adcmux[i] |= 1 << ADLAR; | |||||
| } | |||||
| // If this is the first pin, trigger on timer/counter 1 compare match B. | // If this is the first pin, trigger on timer/counter 1 compare match B. | ||||
| adcsrb[i] = i == 0 ? (1 << ADTS2) | (1 << ADTS0) : 0; | adcsrb[i] = i == 0 ? (1 << ADTS2) | (1 << ADTS0) : 0; | ||||
| #ifdef MUX5 | #ifdef MUX5 | ||||
| if (pin > 7) adcsrb[i] |= (1 << MUX5); | |||||
| if (pin > 7) { | |||||
| adcsrb[i] |= (1 << MUX5); | |||||
| } | |||||
| #endif // MUX5 | #endif // MUX5 | ||||
| adcsra[i] = (1 << ADEN) | (1 << ADIE) | adps; | adcsra[i] = (1 << ADEN) | (1 << ADIE) | adps; | ||||
| adcsra[i] |= i == 0 ? 1 << ADATE : 1 << ADSC; | adcsra[i] |= i == 0 ? 1 << ADATE : 1 << ADSC; | ||||
| ICR1 = ticks - 1; | ICR1 = ticks - 1; | ||||
| // compare for ADC start | // compare for ADC start | ||||
| OCR1B = 0; | OCR1B = 0; | ||||
| // multiply by prescaler | // multiply by prescaler | ||||
| ticks <<= tshift; | ticks <<= tshift; | ||||
| // Sample interval in CPU clock ticks. | // Sample interval in CPU clock ticks. | ||||
| meta->sampleInterval = ticks; | meta->sampleInterval = ticks; | ||||
| meta->cpuFrequency = F_CPU; | meta->cpuFrequency = F_CPU; | ||||
| Serial.print(' '); | Serial.print(' '); | ||||
| Serial.print(meta->pinNumber[i], DEC); | Serial.print(meta->pinNumber[i], DEC); | ||||
| } | } | ||||
| Serial.println(); | |||||
| Serial.println(); | |||||
| Serial.print(F("ADC bits: ")); | Serial.print(F("ADC bits: ")); | ||||
| Serial.println(meta->recordEightBits ? 8 : 10); | Serial.println(meta->recordEightBits ? 8 : 10); | ||||
| Serial.print(F("ADC clock kHz: ")); | Serial.print(F("ADC clock kHz: ")); | ||||
| Serial.println(meta->adcFrequency/1000); | Serial.println(meta->adcFrequency/1000); | ||||
| Serial.print(F("Sample Rate: ")); | Serial.print(F("Sample Rate: ")); | ||||
| Serial.println(sampleRate); | |||||
| Serial.println(sampleRate); | |||||
| Serial.print(F("Sample interval usec: ")); | Serial.print(F("Sample interval usec: ")); | ||||
| Serial.println(1000000.0/sampleRate, 4); | |||||
| Serial.println(1000000.0/sampleRate, 4); | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // enable ADC and timer1 interrupts | // enable ADC and timer1 interrupts | ||||
| // Clear any pending interrupt. | // Clear any pending interrupt. | ||||
| ADCSRA |= 1 << ADIF; | ADCSRA |= 1 << ADIF; | ||||
| // Setup for first pin. | // Setup for first pin. | ||||
| ADMUX = adcmux[0]; | ADMUX = adcmux[0]; | ||||
| ADCSRB = adcsrb[0]; | ADCSRB = adcsrb[0]; | ||||
| ADCSRA = 0; | ADCSRA = 0; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // Convert binary file to CSV file. | |||||
| // Convert binary file to csv file. | |||||
| void binaryToCsv() { | void binaryToCsv() { | ||||
| uint8_t lastPct = 0; | uint8_t lastPct = 0; | ||||
| block_t buf; | block_t buf; | ||||
| uint32_t t0 = millis(); | uint32_t t0 = millis(); | ||||
| char csvName[13]; | char csvName[13]; | ||||
| StdioStream csvStream; | StdioStream csvStream; | ||||
| if (!binFile.isOpen()) { | if (!binFile.isOpen()) { | ||||
| Serial.println(F("No current binary file")); | Serial.println(F("No current binary file")); | ||||
| return; | return; | ||||
| } | } | ||||
| binFile.rewind(); | binFile.rewind(); | ||||
| if (!binFile.read(&buf , 512) == 512) error("Read metadata failed"); | |||||
| // Create a new CSV file. | |||||
| if (!binFile.read(&buf , 512) == 512) { | |||||
| error("Read metadata failed"); | |||||
| } | |||||
| // Create a new csv file. | |||||
| strcpy(csvName, binName); | strcpy(csvName, binName); | ||||
| strcpy_P(&csvName[BASE_NAME_SIZE + 3], PSTR("CSV")); | |||||
| strcpy(&csvName[BASE_NAME_SIZE + 3], "csv"); | |||||
| if (!csvStream.fopen(csvName, "w")) { | if (!csvStream.fopen(csvName, "w")) { | ||||
| error("open csvStream failed"); | |||||
| error("open csvStream failed"); | |||||
| } | } | ||||
| Serial.println(); | Serial.println(); | ||||
| Serial.print(F("Writing: ")); | Serial.print(F("Writing: ")); | ||||
| csvStream.print(intervalMicros, 4); | csvStream.print(intervalMicros, 4); | ||||
| csvStream.println(F(",usec")); | csvStream.println(F(",usec")); | ||||
| for (uint8_t i = 0; i < pm->pinCount; i++) { | for (uint8_t i = 0; i < pm->pinCount; i++) { | ||||
| if (i) csvStream.putc(','); | |||||
| if (i) { | |||||
| csvStream.putc(','); | |||||
| } | |||||
| csvStream.print(F("pin")); | csvStream.print(F("pin")); | ||||
| csvStream.print(pm->pinNumber[i]); | csvStream.print(pm->pinNumber[i]); | ||||
| } | } | ||||
| csvStream.println(); | |||||
| csvStream.println(); | |||||
| uint32_t tPct = millis(); | uint32_t tPct = millis(); | ||||
| while (!Serial.available() && binFile.read(&buf, 512) == 512) { | while (!Serial.available() && binFile.read(&buf, 512) == 512) { | ||||
| uint16_t i; | uint16_t i; | ||||
| if (buf.count == 0) break; | |||||
| if (buf.count == 0) { | |||||
| break; | |||||
| } | |||||
| if (buf.overrun) { | if (buf.overrun) { | ||||
| csvStream.print(F("OVERRUN,")); | csvStream.print(F("OVERRUN,")); | ||||
| csvStream.println(buf.overrun); | |||||
| csvStream.println(buf.overrun); | |||||
| } | } | ||||
| for (uint16_t j = 0; j < buf.count; j += PIN_COUNT) { | for (uint16_t j = 0; j < buf.count; j += PIN_COUNT) { | ||||
| for (uint16_t i = 0; i < PIN_COUNT; i++) { | for (uint16_t i = 0; i < PIN_COUNT; i++) { | ||||
| if (i) csvStream.putc(','); | |||||
| csvStream.print(buf.data[i + j]); | |||||
| if (i) { | |||||
| csvStream.putc(','); | |||||
| } | |||||
| csvStream.print(buf.data[i + j]); | |||||
| } | } | ||||
| csvStream.println(); | csvStream.println(); | ||||
| } | } | ||||
| Serial.println('%'); | Serial.println('%'); | ||||
| } | } | ||||
| } | } | ||||
| if (Serial.available()) break; | |||||
| if (Serial.available()) { | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| csvStream.fclose(); | |||||
| csvStream.fclose(); | |||||
| Serial.print(F("Done: ")); | Serial.print(F("Done: ")); | ||||
| Serial.print(0.001*(millis() - t0)); | Serial.print(0.001*(millis() - t0)); | ||||
| Serial.println(F(" Seconds")); | Serial.println(F(" Seconds")); | ||||
| block_t buf; | block_t buf; | ||||
| uint32_t bgnBlock, endBlock; | uint32_t bgnBlock, endBlock; | ||||
| uint32_t bn = 0; | uint32_t bn = 0; | ||||
| if (!binFile.isOpen()) { | if (!binFile.isOpen()) { | ||||
| Serial.println(F("No current binary file")); | Serial.println(F("No current binary file")); | ||||
| return; | return; | ||||
| } | } | ||||
| bn++; | bn++; | ||||
| while (binFile.read(&buf, 512) == 512) { | while (binFile.read(&buf, 512) == 512) { | ||||
| if (buf.count == 0) break; | |||||
| if (buf.count == 0) { | |||||
| break; | |||||
| } | |||||
| if (buf.overrun) { | if (buf.overrun) { | ||||
| if (!headerPrinted) { | if (!headerPrinted) { | ||||
| Serial.println(); | Serial.println(); | ||||
| Serial.println(F("Type any character to stop")); | Serial.println(F("Type any character to stop")); | ||||
| delay(1000); | delay(1000); | ||||
| while (!Serial.available() && binFile.read(&buf , 512) == 512) { | while (!Serial.available() && binFile.read(&buf , 512) == 512) { | ||||
| if (buf.count == 0) break; | |||||
| if (buf.count == 0) { | |||||
| break; | |||||
| } | |||||
| if (buf.overrun) { | if (buf.overrun) { | ||||
| Serial.print(F("OVERRUN,")); | Serial.print(F("OVERRUN,")); | ||||
| Serial.println(buf.overrun); | Serial.println(buf.overrun); | ||||
| uint32_t const ERASE_SIZE = 262144L; | uint32_t const ERASE_SIZE = 262144L; | ||||
| void logData() { | void logData() { | ||||
| uint32_t bgnBlock, endBlock; | uint32_t bgnBlock, endBlock; | ||||
| // Allocate extra buffer space. | // Allocate extra buffer space. | ||||
| block_t block[BUFFER_BLOCK_COUNT]; | block_t block[BUFFER_BLOCK_COUNT]; | ||||
| Serial.println(); | Serial.println(); | ||||
| // Initialize ADC and timer1. | // Initialize ADC and timer1. | ||||
| adcInit((metadata_t*) &block[0]); | adcInit((metadata_t*) &block[0]); | ||||
| // Find unused file name. | // Find unused file name. | ||||
| if (BASE_NAME_SIZE > 6) { | if (BASE_NAME_SIZE > 6) { | ||||
| error("FILE_BASE_NAME too long"); | error("FILE_BASE_NAME too long"); | ||||
| Serial.println(F("Creating new file")); | Serial.println(F("Creating new file")); | ||||
| binFile.close(); | binFile.close(); | ||||
| if (!binFile.createContiguous(sd.vwd(), | if (!binFile.createContiguous(sd.vwd(), | ||||
| TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) { | |||||
| TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) { | |||||
| error("createContiguous failed"); | error("createContiguous failed"); | ||||
| } | } | ||||
| // Get the address of the file on the SD. | // Get the address of the file on the SD. | ||||
| } | } | ||||
| // Use SdFat's internal buffer. | // Use SdFat's internal buffer. | ||||
| uint8_t* cache = (uint8_t*)sd.vol()->cacheClear(); | uint8_t* cache = (uint8_t*)sd.vol()->cacheClear(); | ||||
| if (cache == 0) error("cacheClear failed"); | |||||
| if (cache == 0) { | |||||
| error("cacheClear failed"); | |||||
| } | |||||
| // Flash erase all data in the file. | // Flash erase all data in the file. | ||||
| Serial.println(F("Erasing all data")); | Serial.println(F("Erasing all data")); | ||||
| uint32_t bgnErase = bgnBlock; | uint32_t bgnErase = bgnBlock; | ||||
| uint32_t endErase; | uint32_t endErase; | ||||
| while (bgnErase < endBlock) { | while (bgnErase < endBlock) { | ||||
| endErase = bgnErase + ERASE_SIZE; | endErase = bgnErase + ERASE_SIZE; | ||||
| if (endErase > endBlock) endErase = endBlock; | |||||
| if (endErase > endBlock) { | |||||
| endErase = endBlock; | |||||
| } | |||||
| if (!sd.card()->erase(bgnErase, endErase)) { | if (!sd.card()->erase(bgnErase, endErase)) { | ||||
| error("erase failed"); | error("erase failed"); | ||||
| } | } | ||||
| // Write metadata. | // Write metadata. | ||||
| if (!sd.card()->writeData((uint8_t*)&block[0])) { | if (!sd.card()->writeData((uint8_t*)&block[0])) { | ||||
| error("Write metadata failed"); | error("Write metadata failed"); | ||||
| } | |||||
| } | |||||
| // Initialize queues. | // Initialize queues. | ||||
| emptyHead = emptyTail = 0; | emptyHead = emptyTail = 0; | ||||
| fullHead = fullTail = 0; | fullHead = fullTail = 0; | ||||
| // Use SdFat buffer for one block. | // Use SdFat buffer for one block. | ||||
| emptyQueue[emptyHead] = (block_t*)cache; | emptyQueue[emptyHead] = (block_t*)cache; | ||||
| emptyHead = queueNext(emptyHead); | emptyHead = queueNext(emptyHead); | ||||
| // Put rest of buffers in the empty queue. | // Put rest of buffers in the empty queue. | ||||
| for (uint8_t i = 0; i < BUFFER_BLOCK_COUNT; i++) { | for (uint8_t i = 0; i < BUFFER_BLOCK_COUNT; i++) { | ||||
| emptyQueue[emptyHead] = &block[i]; | emptyQueue[emptyHead] = &block[i]; | ||||
| if (fullHead != fullTail) { | if (fullHead != fullTail) { | ||||
| // Get address of block to write. | // Get address of block to write. | ||||
| block_t* pBlock = fullQueue[fullTail]; | block_t* pBlock = fullQueue[fullTail]; | ||||
| // Write block to SD. | // Write block to SD. | ||||
| uint32_t usec = micros(); | uint32_t usec = micros(); | ||||
| if (!sd.card()->writeData((uint8_t*)pBlock)) { | if (!sd.card()->writeData((uint8_t*)pBlock)) { | ||||
| } | } | ||||
| usec = micros() - usec; | usec = micros() - usec; | ||||
| t1 = millis(); | t1 = millis(); | ||||
| if (usec > maxLatency) maxLatency = usec; | |||||
| if (usec > maxLatency) { | |||||
| maxLatency = usec; | |||||
| } | |||||
| count += pBlock->count; | count += pBlock->count; | ||||
| // Add overruns and possibly light LED. | |||||
| // Add overruns and possibly light LED. | |||||
| if (pBlock->overrun) { | if (pBlock->overrun) { | ||||
| overruns += pBlock->overrun; | overruns += pBlock->overrun; | ||||
| if (ERROR_LED_PIN >= 0) { | if (ERROR_LED_PIN >= 0) { | ||||
| fullHead = queueNext(fullHead); | fullHead = queueNext(fullHead); | ||||
| isrBuf = 0; | isrBuf = 0; | ||||
| } | } | ||||
| if (fullHead == fullTail) break; | |||||
| if (fullHead == fullTail) { | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| if (!sd.card()->writeStop()) { | if (!sd.card()->writeStop()) { | ||||
| error("writeStop failed"); | error("writeStop failed"); | ||||
| } | } | ||||
| // Truncate file if recording stopped early. | // Truncate file if recording stopped early. | ||||
| if (bn != FILE_BLOCK_COUNT) { | |||||
| if (bn != FILE_BLOCK_COUNT) { | |||||
| Serial.println(F("Truncating file")); | Serial.println(F("Truncating file")); | ||||
| if (!binFile.truncate(512L * bn)) { | if (!binFile.truncate(512L * bn)) { | ||||
| error("Can't truncate file"); | error("Can't truncate file"); | ||||
| } | } | ||||
| } | } | ||||
| if (!binFile.rename(sd.vwd(), binName)) { | if (!binFile.rename(sd.vwd(), binName)) { | ||||
| error("Can't rename file"); | |||||
| } | |||||
| error("Can't rename file"); | |||||
| } | |||||
| Serial.print(F("File renamed: ")); | Serial.print(F("File renamed: ")); | ||||
| Serial.println(binName); | Serial.println(binName); | ||||
| Serial.print(F("Max block write usec: ")); | Serial.print(F("Max block write usec: ")); | ||||
| pinMode(ERROR_LED_PIN, OUTPUT); | pinMode(ERROR_LED_PIN, OUTPUT); | ||||
| } | } | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| // Read the first sample pin to init the ADC. | // Read the first sample pin to init the ADC. | ||||
| analogRead(PIN_LIST[0]); | analogRead(PIN_LIST[0]); | ||||
| Serial.print(F("FreeRam: ")); | Serial.print(F("FreeRam: ")); | ||||
| Serial.println(FreeRam()); | Serial.println(FreeRam()); | ||||
| while (Serial.read() >= 0) {} | while (Serial.read() >= 0) {} | ||||
| Serial.println(); | Serial.println(); | ||||
| Serial.println(F("type:")); | Serial.println(F("type:")); | ||||
| Serial.println(F("c - convert file to CSV")); | |||||
| Serial.println(F("d - dump data to Serial")); | |||||
| Serial.println(F("c - convert file to csv")); | |||||
| Serial.println(F("d - dump data to Serial")); | |||||
| Serial.println(F("e - overrun error details")); | Serial.println(F("e - overrun error details")); | ||||
| Serial.println(F("r - record ADC data")); | Serial.println(F("r - record ADC data")); | ||||
| do { | do { | ||||
| delay(10); | delay(10); | ||||
| } while (Serial.read() >= 0); | } while (Serial.read() >= 0); | ||||
| if (c == 'c') { | if (c == 'c') { | ||||
| binaryToCsv(); | binaryToCsv(); | ||||
| } else if (c == 'd') { | } else if (c == 'd') { | ||||
| dumpData(); | dumpData(); | ||||
| } else if (c == 'e') { | |||||
| } else if (c == 'e') { | |||||
| checkOverrun(); | checkOverrun(); | ||||
| } else if (c == 'r') { | } else if (c == 'r') { | ||||
| logData(); | logData(); |
| // Example use of openNextLFN and open by index. | |||||
| // You can use test files located in | |||||
| // Example use of lfnOpenNext and open by index. | |||||
| // You can use test files located in | |||||
| // SdFat/examples/LongFileName/testFiles. | // SdFat/examples/LongFileName/testFiles. | ||||
| #include<SPI.h> | #include<SPI.h> | ||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| SdFat sd; | SdFat sd; | ||||
| SdFile file; | SdFile file; | ||||
| SdFile dirFile; | |||||
| // Number of files found. | // Number of files found. | ||||
| uint16_t n = 0; | uint16_t n = 0; | ||||
| uint16_t dirIndex[nMax]; | uint16_t dirIndex[nMax]; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void setup() { | void setup() { | ||||
| const size_t NAME_DIM = 50; | |||||
| char name[NAME_DIM]; | |||||
| dir_t dir; | |||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} | while (!Serial) {} | ||||
| delay(1000); | |||||
| // Print the location of some test files. | // Print the location of some test files. | ||||
| Serial.println(F("\r\n" | Serial.println(F("\r\n" | ||||
| "You can use test files located in\r\n" | |||||
| "You can use test files located in\r\n" | |||||
| "SdFat/examples/LongFileName/testFiles")); | "SdFat/examples/LongFileName/testFiles")); | ||||
| if (!sd.begin(SD_CS_PIN)) sd.initErrorHalt(); | |||||
| if (!sd.begin(SD_CS_PIN)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| Serial.print(F("Free RAM: ")); | Serial.print(F("Free RAM: ")); | ||||
| Serial.println(FreeRam()); | Serial.println(FreeRam()); | ||||
| Serial.println(); | Serial.println(); | ||||
| // List files in root directory. Volume working directory is initially root. | |||||
| sd.vwd()->rewind(); | |||||
| while (n < nMax && file.openNextLFN(sd.vwd(), name, NAME_DIM, O_READ) > 0) { | |||||
| // List files in root directory. | |||||
| if (!dirFile.open("/", O_READ)) { | |||||
| sd.errorHalt("open root failed"); | |||||
| } | |||||
| while (n < nMax && file.openNext(&dirFile, O_READ)) { | |||||
| // Skip directories and hidden files. | // Skip directories and hidden files. | ||||
| if (!file.isSubDir() && !file.isHidden()) { | if (!file.isSubDir() && !file.isHidden()) { | ||||
| // Save dirIndex of file in directory. | // Save dirIndex of file in directory. | ||||
| dirIndex[n] = file.dirIndex(); | dirIndex[n] = file.dirIndex(); | ||||
| // Print the file number and name. | // Print the file number and name. | ||||
| Serial.print(n++); | Serial.print(n++); | ||||
| Serial.write(' '); | Serial.write(' '); | ||||
| Serial.println(name); | |||||
| file.printName(&Serial); | |||||
| Serial.println(); | |||||
| } | } | ||||
| file.close(); | file.close(); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void loop() { | void loop() { | ||||
| int c; | int c; | ||||
| // Discard any Serial input. | // Discard any Serial input. | ||||
| while (Serial.read() > 0) {} | while (Serial.read() > 0) {} | ||||
| Serial.print(F("\r\nEnter File Number: ")); | Serial.print(F("\r\nEnter File Number: ")); | ||||
| while ((c = Serial.read()) < 0) {}; | while ((c = Serial.read()) < 0) {}; | ||||
| if (!isdigit(c) || (c -= '0') >= n) { | if (!isdigit(c) || (c -= '0') >= n) { | ||||
| Serial.println(F("Invald number")); | Serial.println(F("Invald number")); | ||||
| return; | return; | ||||
| } | } | ||||
| Serial.println(c); | Serial.println(c); | ||||
| if (!file.open(sd.vwd(), dirIndex[c], O_READ)) { | |||||
| if (!file.open(&dirFile, dirIndex[c], O_READ)) { | |||||
| sd.errorHalt(F("open")); | sd.errorHalt(F("open")); | ||||
| } | } | ||||
| Serial.println(); | Serial.println(); | ||||
| char last; | char last; | ||||
| // Copy up to 500 characters to Serial. | // Copy up to 500 characters to Serial. | ||||
| for (int i = 0; i < 500 && (c = file.read()) > 0; i++) { | for (int i = 0; i < 500 && (c = file.read()) > 0; i++) { | ||||
| Serial.write(last = (char)c); | Serial.write(last = (char)c); | ||||
| } | } | ||||
| // Add new line if missing from last line. | // Add new line if missing from last line. | ||||
| if (last != '\n') Serial.println(); | |||||
| if (last != '\n') { | |||||
| Serial.println(); | |||||
| } | |||||
| file.close(); | file.close(); | ||||
| Serial.flush(); | |||||
| delay(100); | |||||
| } | } |
| /** | /** | ||||
| * This program logs data to a binary file. Functions are included | * This program logs data to a binary file. Functions are included | ||||
| * to convert the binary file to a CSV text file. | |||||
| * to convert the binary file to a csv text file. | |||||
| * | * | ||||
| * Samples are logged at regular intervals. The maximum logging rate | * Samples are logged at regular intervals. The maximum logging rate | ||||
| * depends on the quality of your SD card and the time required to | * depends on the quality of your SD card and the time required to | ||||
| * | * | ||||
| * Data is written to the file using a SD multiple block write command. | * Data is written to the file using a SD multiple block write command. | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| #include <SdFatUtil.h> | #include <SdFatUtil.h> | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void printData(Print* pr, data_t* data) { | void printData(Print* pr, data_t* data) { | ||||
| pr->print(data->time); | pr->print(data->time); | ||||
| for (int i = 0; i < ADC_DIM; i++) { | for (int i = 0; i < ADC_DIM; i++) { | ||||
| pr->write(','); | |||||
| pr->write(','); | |||||
| pr->print(data->adc[i]); | pr->print(data->adc[i]); | ||||
| } | } | ||||
| pr->println(); | pr->println(); | ||||
| // Digital pin to indicate an error, set to -1 if not used. | // Digital pin to indicate an error, set to -1 if not used. | ||||
| // The led blinks for fatal errors. The led goes on solid for SD write | // The led blinks for fatal errors. The led goes on solid for SD write | ||||
| // overrun errors and logging continues. | // overrun errors and logging continues. | ||||
| const int8_t ERROR_LED_PIN = 3; | |||||
| const int8_t ERROR_LED_PIN = -1; | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // File definitions. | // File definitions. | ||||
| // | // | ||||
| const uint32_t FILE_BLOCK_COUNT = 256000; | const uint32_t FILE_BLOCK_COUNT = 256000; | ||||
| // log file base name. Must be six characters or less. | // log file base name. Must be six characters or less. | ||||
| #define FILE_BASE_NAME "DATA" | |||||
| #define FILE_BASE_NAME "data" | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // Buffer definitions. | // Buffer definitions. | ||||
| // | // | ||||
| // The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT additional | |||||
| // The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT additional | |||||
| // buffers. | // buffers. | ||||
| // | // | ||||
| #ifndef RAMEND | #ifndef RAMEND | ||||
| // End of configuration constants. | // End of configuration constants. | ||||
| //============================================================================== | //============================================================================== | ||||
| // Temporary log file. Will be deleted if a reset or power failure occurs. | // Temporary log file. Will be deleted if a reset or power failure occurs. | ||||
| #define TMP_FILE_NAME "TMP_LOG.BIN" | |||||
| #define TMP_FILE_NAME "tmp_log.bin" | |||||
| // Size of file base name. Must not be larger than six. | // Size of file base name. Must not be larger than six. | ||||
| const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1; | const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1; | ||||
| SdBaseFile binFile; | SdBaseFile binFile; | ||||
| char binName[13] = FILE_BASE_NAME "00.BIN"; | |||||
| char binName[13] = FILE_BASE_NAME "00.bin"; | |||||
| // Number of data records in a block. | // Number of data records in a block. | ||||
| const uint16_t DATA_DIM = (512 - 4)/sizeof(data_t); | const uint16_t DATA_DIM = (512 - 4)/sizeof(data_t); | ||||
| uint8_t fullTail; | uint8_t fullTail; | ||||
| // Advance queue index. | // Advance queue index. | ||||
| inline uint8_t queueNext(uint8_t ht) {return ht < (QUEUE_DIM - 1) ? ht + 1 : 0;} | |||||
| inline uint8_t queueNext(uint8_t ht) { | |||||
| return ht < (QUEUE_DIM - 1) ? ht + 1 : 0; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| // Error messages stored in flash. | // Error messages stored in flash. | ||||
| #define error(msg) errorFlash(F(msg)) | #define error(msg) errorFlash(F(msg)) | ||||
| } | } | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| // Convert binary file to CSV file. | |||||
| // Convert binary file to csv file. | |||||
| void binaryToCsv() { | void binaryToCsv() { | ||||
| uint8_t lastPct = 0; | uint8_t lastPct = 0; | ||||
| block_t block; | block_t block; | ||||
| uint32_t syncCluster = 0; | uint32_t syncCluster = 0; | ||||
| SdFile csvFile; | SdFile csvFile; | ||||
| char csvName[13]; | char csvName[13]; | ||||
| if (!binFile.isOpen()) { | if (!binFile.isOpen()) { | ||||
| Serial.println(); | Serial.println(); | ||||
| Serial.println(F("No current binary file")); | Serial.println(F("No current binary file")); | ||||
| binFile.rewind(); | binFile.rewind(); | ||||
| // Create a new csvFile. | // Create a new csvFile. | ||||
| strcpy(csvName, binName); | strcpy(csvName, binName); | ||||
| strcpy_P(&csvName[BASE_NAME_SIZE + 3], PSTR("CSV")); | |||||
| strcpy(&csvName[BASE_NAME_SIZE + 3], "csv"); | |||||
| if (!csvFile.open(csvName, O_WRITE | O_CREAT | O_TRUNC)) { | if (!csvFile.open(csvName, O_WRITE | O_CREAT | O_TRUNC)) { | ||||
| error("open csvFile failed"); | |||||
| error("open csvFile failed"); | |||||
| } | } | ||||
| Serial.println(); | Serial.println(); | ||||
| Serial.print(F("Writing: ")); | Serial.print(F("Writing: ")); | ||||
| uint32_t tPct = millis(); | uint32_t tPct = millis(); | ||||
| while (!Serial.available() && binFile.read(&block, 512) == 512) { | while (!Serial.available() && binFile.read(&block, 512) == 512) { | ||||
| uint16_t i; | uint16_t i; | ||||
| if (block.count == 0) break; | |||||
| if (block.count == 0) { | |||||
| break; | |||||
| } | |||||
| if (block.overrun) { | if (block.overrun) { | ||||
| csvFile.print(F("OVERRUN,")); | csvFile.print(F("OVERRUN,")); | ||||
| csvFile.println(block.overrun); | csvFile.println(block.overrun); | ||||
| Serial.println('%'); | Serial.println('%'); | ||||
| } | } | ||||
| } | } | ||||
| if (Serial.available()) break; | |||||
| if (Serial.available()) { | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| csvFile.close(); | csvFile.close(); | ||||
| Serial.print(F("Done: ")); | Serial.print(F("Done: ")); | ||||
| block_t block; | block_t block; | ||||
| uint32_t bgnBlock, endBlock; | uint32_t bgnBlock, endBlock; | ||||
| uint32_t bn = 0; | uint32_t bn = 0; | ||||
| if (!binFile.isOpen()) { | if (!binFile.isOpen()) { | ||||
| Serial.println(); | Serial.println(); | ||||
| Serial.println(F("No current binary file")); | Serial.println(F("No current binary file")); | ||||
| Serial.println(); | Serial.println(); | ||||
| Serial.println(F("Checking overrun errors - type any character to stop")); | Serial.println(F("Checking overrun errors - type any character to stop")); | ||||
| while (binFile.read(&block, 512) == 512) { | while (binFile.read(&block, 512) == 512) { | ||||
| if (block.count == 0) break; | |||||
| if (block.count == 0) { | |||||
| break; | |||||
| } | |||||
| if (block.overrun) { | if (block.overrun) { | ||||
| if (!headerPrinted) { | if (!headerPrinted) { | ||||
| Serial.println(); | Serial.println(); | ||||
| delay(1000); | delay(1000); | ||||
| printHeader(&Serial); | printHeader(&Serial); | ||||
| while (!Serial.available() && binFile.read(&block , 512) == 512) { | while (!Serial.available() && binFile.read(&block , 512) == 512) { | ||||
| if (block.count == 0) break; | |||||
| if (block.count == 0) { | |||||
| break; | |||||
| } | |||||
| if (block.overrun) { | if (block.overrun) { | ||||
| Serial.print(F("OVERRUN,")); | Serial.print(F("OVERRUN,")); | ||||
| Serial.println(block.overrun); | Serial.println(block.overrun); | ||||
| uint32_t const ERASE_SIZE = 262144L; | uint32_t const ERASE_SIZE = 262144L; | ||||
| void logData() { | void logData() { | ||||
| uint32_t bgnBlock, endBlock; | uint32_t bgnBlock, endBlock; | ||||
| // Allocate extra buffer space. | // Allocate extra buffer space. | ||||
| block_t block[BUFFER_BLOCK_COUNT]; | block_t block[BUFFER_BLOCK_COUNT]; | ||||
| block_t* curBlock = 0; | block_t* curBlock = 0; | ||||
| Serial.println(); | Serial.println(); | ||||
| // Find unused file name. | // Find unused file name. | ||||
| if (BASE_NAME_SIZE > 6) { | if (BASE_NAME_SIZE > 6) { | ||||
| error("FILE_BASE_NAME too long"); | error("FILE_BASE_NAME too long"); | ||||
| Serial.println(F("Creating new file")); | Serial.println(F("Creating new file")); | ||||
| binFile.close(); | binFile.close(); | ||||
| if (!binFile.createContiguous(sd.vwd(), | if (!binFile.createContiguous(sd.vwd(), | ||||
| TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) { | |||||
| TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) { | |||||
| error("createContiguous failed"); | error("createContiguous failed"); | ||||
| } | } | ||||
| // Get the address of the file on the SD. | // Get the address of the file on the SD. | ||||
| } | } | ||||
| // Use SdFat's internal buffer. | // Use SdFat's internal buffer. | ||||
| uint8_t* cache = (uint8_t*)sd.vol()->cacheClear(); | uint8_t* cache = (uint8_t*)sd.vol()->cacheClear(); | ||||
| if (cache == 0) error("cacheClear failed"); | |||||
| if (cache == 0) { | |||||
| error("cacheClear failed"); | |||||
| } | |||||
| // Flash erase all data in the file. | // Flash erase all data in the file. | ||||
| Serial.println(F("Erasing all data")); | Serial.println(F("Erasing all data")); | ||||
| uint32_t bgnErase = bgnBlock; | uint32_t bgnErase = bgnBlock; | ||||
| uint32_t endErase; | uint32_t endErase; | ||||
| while (bgnErase < endBlock) { | while (bgnErase < endBlock) { | ||||
| endErase = bgnErase + ERASE_SIZE; | endErase = bgnErase + ERASE_SIZE; | ||||
| if (endErase > endBlock) endErase = endBlock; | |||||
| if (endErase > endBlock) { | |||||
| endErase = endBlock; | |||||
| } | |||||
| if (!sd.card()->erase(bgnErase, endErase)) { | if (!sd.card()->erase(bgnErase, endErase)) { | ||||
| error("erase failed"); | error("erase failed"); | ||||
| } | } | ||||
| // Initialize queues. | // Initialize queues. | ||||
| emptyHead = emptyTail = 0; | emptyHead = emptyTail = 0; | ||||
| fullHead = fullTail = 0; | fullHead = fullTail = 0; | ||||
| // Use SdFat buffer for one block. | // Use SdFat buffer for one block. | ||||
| emptyQueue[emptyHead] = (block_t*)cache; | emptyQueue[emptyHead] = (block_t*)cache; | ||||
| emptyHead = queueNext(emptyHead); | emptyHead = queueNext(emptyHead); | ||||
| // Put rest of buffers in the empty queue. | // Put rest of buffers in the empty queue. | ||||
| for (uint8_t i = 0; i < BUFFER_BLOCK_COUNT; i++) { | for (uint8_t i = 0; i < BUFFER_BLOCK_COUNT; i++) { | ||||
| emptyQueue[emptyHead] = &block[i]; | emptyQueue[emptyHead] = &block[i]; | ||||
| while (1) { | while (1) { | ||||
| // Time for next data record. | // Time for next data record. | ||||
| logTime += LOG_INTERVAL_USEC; | logTime += LOG_INTERVAL_USEC; | ||||
| if (Serial.available()) closeFile = true; | |||||
| if (Serial.available()) { | |||||
| closeFile = true; | |||||
| } | |||||
| if (closeFile) { | if (closeFile) { | ||||
| if (curBlock != 0 && curBlock->count >= 0) { | |||||
| if (curBlock != 0 && curBlock->count >= 0) { | |||||
| // Put buffer in full queue. | // Put buffer in full queue. | ||||
| fullQueue[fullHead] = curBlock; | fullQueue[fullHead] = curBlock; | ||||
| fullHead = queueNext(fullHead); | fullHead = queueNext(fullHead); | ||||
| curBlock = 0; | curBlock = 0; | ||||
| } | |||||
| } | |||||
| } else { | } else { | ||||
| if (curBlock == 0 && emptyTail != emptyHead) { | if (curBlock == 0 && emptyTail != emptyHead) { | ||||
| curBlock = emptyQueue[emptyTail]; | curBlock = emptyQueue[emptyTail]; | ||||
| do { | do { | ||||
| diff = logTime - micros(); | diff = logTime - micros(); | ||||
| } while(diff > 0); | } while(diff > 0); | ||||
| if (diff < -10) error("LOG_INTERVAL_USEC too small"); | |||||
| if (diff < -10) { | |||||
| error("LOG_INTERVAL_USEC too small"); | |||||
| } | |||||
| if (curBlock == 0) { | if (curBlock == 0) { | ||||
| overrun++; | overrun++; | ||||
| } else { | } else { | ||||
| acquireData(&curBlock->data[curBlock->count++]); | acquireData(&curBlock->data[curBlock->count++]); | ||||
| if (curBlock->count == DATA_DIM) { | if (curBlock->count == DATA_DIM) { | ||||
| fullQueue[fullHead] = curBlock; | fullQueue[fullHead] = curBlock; | ||||
| fullHead = queueNext(fullHead); | |||||
| fullHead = queueNext(fullHead); | |||||
| curBlock = 0; | curBlock = 0; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| if (fullHead == fullTail) { | if (fullHead == fullTail) { | ||||
| // Exit loop if done. | // Exit loop if done. | ||||
| if (closeFile) break; | |||||
| if (closeFile) { | |||||
| break; | |||||
| } | |||||
| } else if (!sd.card()->isBusy()) { | } else if (!sd.card()->isBusy()) { | ||||
| // Get address of block to write. | // Get address of block to write. | ||||
| block_t* pBlock = fullQueue[fullTail]; | block_t* pBlock = fullQueue[fullTail]; | ||||
| fullTail = queueNext(fullTail); | |||||
| fullTail = queueNext(fullTail); | |||||
| // Write block to SD. | // Write block to SD. | ||||
| uint32_t usec = micros(); | uint32_t usec = micros(); | ||||
| if (!sd.card()->writeData((uint8_t*)pBlock)) { | if (!sd.card()->writeData((uint8_t*)pBlock)) { | ||||
| } | } | ||||
| usec = micros() - usec; | usec = micros() - usec; | ||||
| t1 = millis(); | t1 = millis(); | ||||
| if (usec > maxLatency) maxLatency = usec; | |||||
| if (usec > maxLatency) { | |||||
| maxLatency = usec; | |||||
| } | |||||
| count += pBlock->count; | count += pBlock->count; | ||||
| // Add overruns and possibly light LED. | |||||
| // Add overruns and possibly light LED. | |||||
| if (pBlock->overrun) { | if (pBlock->overrun) { | ||||
| overrunTotal += pBlock->overrun; | overrunTotal += pBlock->overrun; | ||||
| if (ERROR_LED_PIN >= 0) { | if (ERROR_LED_PIN >= 0) { | ||||
| error("writeStop failed"); | error("writeStop failed"); | ||||
| } | } | ||||
| // Truncate file if recording stopped early. | // Truncate file if recording stopped early. | ||||
| if (bn != FILE_BLOCK_COUNT) { | |||||
| if (bn != FILE_BLOCK_COUNT) { | |||||
| Serial.println(F("Truncating file")); | Serial.println(F("Truncating file")); | ||||
| if (!binFile.truncate(512L * bn)) { | if (!binFile.truncate(512L * bn)) { | ||||
| error("Can't truncate file"); | error("Can't truncate file"); | ||||
| } | } | ||||
| } | } | ||||
| if (!binFile.rename(sd.vwd(), binName)) { | if (!binFile.rename(sd.vwd(), binName)) { | ||||
| error("Can't rename file"); | |||||
| } | |||||
| error("Can't rename file"); | |||||
| } | |||||
| Serial.print(F("File renamed: ")); | Serial.print(F("File renamed: ")); | ||||
| Serial.println(binName); | Serial.println(binName); | ||||
| Serial.print(F("Max block write usec: ")); | Serial.print(F("Max block write usec: ")); | ||||
| } | } | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} | while (!Serial) {} | ||||
| Serial.print(F("FreeRam: ")); | Serial.print(F("FreeRam: ")); | ||||
| Serial.println(FreeRam()); | Serial.println(FreeRam()); | ||||
| Serial.print(F("Records/block: ")); | Serial.print(F("Records/block: ")); | ||||
| Serial.println(DATA_DIM); | Serial.println(DATA_DIM); | ||||
| if (sizeof(block_t) != 512) error("Invalid block size"); | |||||
| if (sizeof(block_t) != 512) { | |||||
| error("Invalid block size"); | |||||
| } | |||||
| // initialize file system. | // initialize file system. | ||||
| if (!sd.begin(SD_CS_PIN, SPI_FULL_SPEED)) { | if (!sd.begin(SD_CS_PIN, SPI_FULL_SPEED)) { | ||||
| sd.initErrorPrint(); | sd.initErrorPrint(); | ||||
| while (Serial.read() >= 0) {} | while (Serial.read() >= 0) {} | ||||
| Serial.println(); | Serial.println(); | ||||
| Serial.println(F("type:")); | Serial.println(F("type:")); | ||||
| Serial.println(F("c - convert file to CSV")); | |||||
| Serial.println(F("d - dump data to Serial")); | |||||
| Serial.println(F("c - convert file to csv")); | |||||
| Serial.println(F("d - dump data to Serial")); | |||||
| Serial.println(F("e - overrun error details")); | Serial.println(F("e - overrun error details")); | ||||
| Serial.println(F("r - record data")); | Serial.println(F("r - record data")); | ||||
| while(!Serial.available()) {} | while(!Serial.available()) {} | ||||
| char c = tolower(Serial.read()); | char c = tolower(Serial.read()); | ||||
| // Discard extra Serial data. | // Discard extra Serial data. | ||||
| do { | do { | ||||
| delay(10); | delay(10); | ||||
| } while (Serial.read() >= 0); | } while (Serial.read() >= 0); | ||||
| if (ERROR_LED_PIN >= 0) { | if (ERROR_LED_PIN >= 0) { | ||||
| digitalWrite(ERROR_LED_PIN, LOW); | digitalWrite(ERROR_LED_PIN, LOW); | ||||
| } | } | ||||
| binaryToCsv(); | binaryToCsv(); | ||||
| } else if (c == 'd') { | } else if (c == 'd') { | ||||
| dumpData(); | dumpData(); | ||||
| } else if (c == 'e') { | |||||
| } else if (c == 'e') { | |||||
| checkOverrun(); | checkOverrun(); | ||||
| } else if (c == 'r') { | } else if (c == 'r') { | ||||
| logData(); | logData(); |
| /* | /* | ||||
| * Print size, modify date/time, and name for all files in root. | * Print size, modify date/time, and name for all files in root. | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // SD chip select pin | // SD chip select pin | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| delay(1000); | delay(1000); | ||||
| Serial.println(); | |||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // open next file in root. The volume working directory, vwd, is root | // open next file in root. The volume working directory, vwd, is root | ||||
| while (file.openNext(sd.vwd(), O_READ)) { | while (file.openNext(sd.vwd(), O_READ)) { | ||||
| file.printFileSize(&Serial); | file.printFileSize(&Serial); | ||||
| Serial.write(' '); | Serial.write(' '); | ||||
| file.printModifyDateTime(&Serial); | file.printModifyDateTime(&Serial); | ||||
| Serial.write(' '); | |||||
| Serial.write(' '); | |||||
| file.printName(&Serial); | file.printName(&Serial); | ||||
| if (file.isDir()) { | if (file.isDir()) { | ||||
| // Indicate a directory. | // Indicate a directory. |
| /* | /* | ||||
| * This sketch is a simple Print benchmark. | |||||
| * This program is a simple Print benchmark. | |||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| #include <SdFatUtil.h> | #include <SdFatUtil.h> | ||||
| while (Serial.read() >= 0) { | while (Serial.read() >= 0) { | ||||
| } | } | ||||
| // pstr stores strings in flash to save RAM | // pstr stores strings in flash to save RAM | ||||
| cout << pstr("Type any character to start\n"); | |||||
| cout << F("Type any character to start\n"); | |||||
| while (Serial.read() <= 0) { | while (Serial.read() <= 0) { | ||||
| } | } | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| cout << pstr("Free RAM: ") << FreeRam() << endl; | |||||
| cout << F("Free RAM: ") << FreeRam() << endl; | |||||
| // initialize the SD card at SPI_FULL_SPEED for best performance. | // initialize the SD card at SPI_FULL_SPEED for best performance. | ||||
| // try SPI_HALF_SPEED if bus errors occur. | // try SPI_HALF_SPEED if bus errors occur. | ||||
| if (!sd.begin(chipSelect, SPI_FULL_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_FULL_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| cout << pstr("Type is FAT") << int(sd.vol()->fatType()) << endl; | |||||
| cout << F("Type is FAT") << int(sd.vol()->fatType()) << endl; | |||||
| cout << pstr("Starting print test. Please wait.\n\n"); | |||||
| cout << F("Starting print test. Please wait.\n\n"); | |||||
| // do write test | // do write test | ||||
| for (int test = 0; test < 6; test++) { | for (int test = 0; test < 6; test++) { | ||||
| char fileName[13] = "BENCH0.TXT"; | |||||
| char fileName[13] = "bench0.txt"; | |||||
| fileName[5] = '0' + test; | fileName[5] = '0' + test; | ||||
| // open or create file - truncate existing file. | // open or create file - truncate existing file. | ||||
| if (!file.open(fileName, O_CREAT | O_TRUNC | O_RDWR)) { | if (!file.open(fileName, O_CREAT | O_TRUNC | O_RDWR)) { | ||||
| totalLatency = 0; | totalLatency = 0; | ||||
| switch(test) { | switch(test) { | ||||
| case 0: | case 0: | ||||
| cout << pstr("Test of println(uint16_t)\n"); | |||||
| cout << F("Test of println(uint16_t)\n"); | |||||
| break; | break; | ||||
| case 1: | case 1: | ||||
| cout << pstr("Test of printField(uint16_t, char)\n"); | |||||
| cout << F("Test of printField(uint16_t, char)\n"); | |||||
| break; | break; | ||||
| case 2: | case 2: | ||||
| cout << pstr("Test of println(uint32_t)\n"); | |||||
| cout << F("Test of println(uint32_t)\n"); | |||||
| break; | break; | ||||
| case 3: | case 3: | ||||
| cout << pstr("Test of printField(uint32_t, char)\n"); | |||||
| break; | |||||
| cout << F("Test of printField(uint32_t, char)\n"); | |||||
| break; | |||||
| case 4: | case 4: | ||||
| cout << pstr("Test of println(float)\n"); | |||||
| break; | |||||
| cout << F("Test of println(float)\n"); | |||||
| break; | |||||
| case 5: | case 5: | ||||
| cout << pstr("Test of printField(float, char)\n"); | |||||
| break; | |||||
| cout << F("Test of printField(float, char)\n"); | |||||
| break; | |||||
| } | } | ||||
| uint32_t t = millis(); | uint32_t t = millis(); | ||||
| for (uint16_t i = 0; i < N_PRINT; i++) { | for (uint16_t i = 0; i < N_PRINT; i++) { | ||||
| uint32_t m = micros(); | uint32_t m = micros(); | ||||
| case 2: | case 2: | ||||
| file.println(12345678UL + i); | file.println(12345678UL + i); | ||||
| break; | |||||
| break; | |||||
| case 3: | case 3: | ||||
| file.printField(12345678UL + i, '\n'); | file.printField(12345678UL + i, '\n'); | ||||
| break; | |||||
| break; | |||||
| case 4: | case 4: | ||||
| file.println((float)0.01*i); | file.println((float)0.01*i); | ||||
| break; | break; | ||||
| case 5: | case 5: | ||||
| file.printField((float)0.01*i, '\n'); | file.printField((float)0.01*i, '\n'); | ||||
| break; | break; | ||||
| error("write failed"); | error("write failed"); | ||||
| } | } | ||||
| m = micros() - m; | m = micros() - m; | ||||
| if (maxLatency < m) maxLatency = m; | |||||
| if (minLatency > m) minLatency = m; | |||||
| if (maxLatency < m) { | |||||
| maxLatency = m; | |||||
| } | |||||
| if (minLatency > m) { | |||||
| minLatency = m; | |||||
| } | |||||
| totalLatency += m; | totalLatency += m; | ||||
| } | } | ||||
| file.close(); | file.close(); | ||||
| t = millis() - t; | t = millis() - t; | ||||
| double s = file.fileSize(); | double s = file.fileSize(); | ||||
| cout << pstr("Time ") << 0.001*t << pstr(" sec\n"); | |||||
| cout << pstr("File size ") << 0.001*s << pstr(" KB\n"); | |||||
| cout << pstr("Write ") << s/t << pstr(" KB/sec\n"); | |||||
| cout << pstr("Maximum latency: ") << maxLatency; | |||||
| cout << pstr(" usec, Minimum Latency: ") << minLatency; | |||||
| cout << pstr(" usec, Avg Latency: "); | |||||
| cout << totalLatency/N_PRINT << pstr(" usec\n\n"); | |||||
| cout << F("Time ") << 0.001*t << F(" sec\n"); | |||||
| cout << F("File size ") << 0.001*s << F(" KB\n"); | |||||
| cout << F("Write ") << s/t << F(" KB/sec\n"); | |||||
| cout << F("Maximum latency: ") << maxLatency; | |||||
| cout << F(" usec, Minimum Latency: ") << minLatency; | |||||
| cout << F(" usec, Avg Latency: "); | |||||
| cout << totalLatency/N_PRINT << F(" usec\n\n"); | |||||
| } | } | ||||
| cout << pstr("Done!\n\n"); | |||||
| cout << F("Done!\n\n"); | |||||
| } | } |
| // Quick hardware test. | // Quick hardware test. | ||||
| // | // | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // | // | ||||
| // Set DISABLE_CHIP_SELECT to disable a second SPI device. | // Set DISABLE_CHIP_SELECT to disable a second SPI device. | ||||
| int chipSelect; | int chipSelect; | ||||
| void cardOrSpeed() { | void cardOrSpeed() { | ||||
| cout << pstr("Try another SD card or reduce the SPI bus speed.\n"); | |||||
| cout << pstr("Edit spiSpeed in this sketch to change it.\n"); | |||||
| cout << F("Try another SD card or reduce the SPI bus speed.\n"); | |||||
| cout << F("Edit spiSpeed in this program to change it.\n"); | |||||
| } | } | ||||
| void reformatMsg() { | void reformatMsg() { | ||||
| cout << pstr("Try reformatting the card. For best results use\n"); | |||||
| cout << pstr("the SdFormatter sketch in SdFat/examples or download\n"); | |||||
| cout << pstr("and use SDFormatter from www.sdcard.org/downloads.\n"); | |||||
| cout << F("Try reformatting the card. For best results use\n"); | |||||
| cout << F("the SdFormatter program in SdFat/examples or download\n"); | |||||
| cout << F("and use SDFormatter from www.sdcard.org/downloads.\n"); | |||||
| } | } | ||||
| void setup() { | void setup() { | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // Wait for Leonardo. | while (!Serial) {} // Wait for Leonardo. | ||||
| cout << pstr("\nSPI pins:\n"); | |||||
| cout << pstr("MISO: ") << int(MISO) << endl; | |||||
| cout << pstr("MOSI: ") << int(MOSI) << endl; | |||||
| cout << pstr("SCK: ") << int(SCK) << endl; | |||||
| cout << pstr("SS: ") << int(SS) << endl; | |||||
| cout << F("\nSPI pins:\n"); | |||||
| cout << F("MISO: ") << int(MISO) << endl; | |||||
| cout << F("MOSI: ") << int(MOSI) << endl; | |||||
| cout << F("SCK: ") << int(SCK) << endl; | |||||
| cout << F("SS: ") << int(SS) << endl; | |||||
| if (DISABLE_CHIP_SELECT < 0) { | if (DISABLE_CHIP_SELECT < 0) { | ||||
| cout << pstr( | |||||
| "\nBe sure to edit DISABLE_CHIP_SELECT if you have\n" | |||||
| "a second SPI device. For example, with the Ethernet\n" | |||||
| "shield, DISABLE_CHIP_SELECT should be set to 10\n" | |||||
| "to disable the Ethernet controller.\n"); | |||||
| cout << F( | |||||
| "\nBe sure to edit DISABLE_CHIP_SELECT if you have\n" | |||||
| "a second SPI device. For example, with the Ethernet\n" | |||||
| "shield, DISABLE_CHIP_SELECT should be set to 10\n" | |||||
| "to disable the Ethernet controller.\n"); | |||||
| } | } | ||||
| cout << pstr( | |||||
| "\nSD chip select is the key hardware option.\n" | |||||
| "Common values are:\n" | |||||
| "Arduino Ethernet shield, pin 4\n" | |||||
| "Sparkfun SD shield, pin 8\n" | |||||
| "Adafruit SD shields and modules, pin 10\n"); | |||||
| cout << F( | |||||
| "\nSD chip select is the key hardware option.\n" | |||||
| "Common values are:\n" | |||||
| "Arduino Ethernet shield, pin 4\n" | |||||
| "Sparkfun SD shield, pin 8\n" | |||||
| "Adafruit SD shields and modules, pin 10\n"); | |||||
| } | } | ||||
| bool firstTry = true; | bool firstTry = true; | ||||
| // read any existing Serial data | // read any existing Serial data | ||||
| while (Serial.read() >= 0) {} | while (Serial.read() >= 0) {} | ||||
| if (!firstTry) cout << pstr("\nRestarting\n"); | |||||
| if (!firstTry) { | |||||
| cout << F("\nRestarting\n"); | |||||
| } | |||||
| firstTry = false; | firstTry = false; | ||||
| cout << pstr("\nEnter the chip select pin number: "); | |||||
| cout << F("\nEnter the chip select pin number: "); | |||||
| while (!Serial.available()) {} | while (!Serial.available()) {} | ||||
| delay(400); // catch Due restart problem | |||||
| delay(400); // catch Due restart problem | |||||
| cin.readline(); | cin.readline(); | ||||
| if (cin >> chipSelect) { | if (cin >> chipSelect) { | ||||
| cout << chipSelect << endl; | cout << chipSelect << endl; | ||||
| } else { | } else { | ||||
| cout << pstr("\nInvalid pin number\n"); | |||||
| cout << F("\nInvalid pin number\n"); | |||||
| return; | return; | ||||
| } | } | ||||
| if (DISABLE_CHIP_SELECT < 0) { | if (DISABLE_CHIP_SELECT < 0) { | ||||
| cout << pstr( | |||||
| "\nAssuming the SD is the only SPI device.\n" | |||||
| "Edit DISABLE_CHIP_SELECT to disable another device.\n"); | |||||
| cout << F( | |||||
| "\nAssuming the SD is the only SPI device.\n" | |||||
| "Edit DISABLE_CHIP_SELECT to disable another device.\n"); | |||||
| } else { | } else { | ||||
| cout << pstr("\nDisabling SPI device on pin "); | |||||
| cout << F("\nDisabling SPI device on pin "); | |||||
| cout << int(DISABLE_CHIP_SELECT) << endl; | cout << int(DISABLE_CHIP_SELECT) << endl; | ||||
| pinMode(DISABLE_CHIP_SELECT, OUTPUT); | pinMode(DISABLE_CHIP_SELECT, OUTPUT); | ||||
| digitalWrite(DISABLE_CHIP_SELECT, HIGH); | digitalWrite(DISABLE_CHIP_SELECT, HIGH); | ||||
| } | } | ||||
| if (!sd.begin(chipSelect, spiSpeed)) { | if (!sd.begin(chipSelect, spiSpeed)) { | ||||
| if (sd.card()->errorCode()) { | if (sd.card()->errorCode()) { | ||||
| cout << pstr( | |||||
| "\nSD initialization failed.\n" | |||||
| "Do not reformat the card!\n" | |||||
| "Is the card correctly inserted?\n" | |||||
| "Is chipSelect set to the correct value?\n" | |||||
| "Does another SPI device need to be disabled?\n" | |||||
| "Is there a wiring/soldering problem?\n"); | |||||
| cout << pstr("\nerrorCode: ") << hex << showbase; | |||||
| cout << F( | |||||
| "\nSD initialization failed.\n" | |||||
| "Do not reformat the card!\n" | |||||
| "Is the card correctly inserted?\n" | |||||
| "Is chipSelect set to the correct value?\n" | |||||
| "Does another SPI device need to be disabled?\n" | |||||
| "Is there a wiring/soldering problem?\n"); | |||||
| cout << F("\nerrorCode: ") << hex << showbase; | |||||
| cout << int(sd.card()->errorCode()); | cout << int(sd.card()->errorCode()); | ||||
| cout << pstr(", errorData: ") << int(sd.card()->errorData()); | |||||
| cout << F(", errorData: ") << int(sd.card()->errorData()); | |||||
| cout << dec << noshowbase << endl; | cout << dec << noshowbase << endl; | ||||
| return; | return; | ||||
| } | } | ||||
| cout << pstr("\nCard successfully initialized.\n"); | |||||
| cout << F("\nCard successfully initialized.\n"); | |||||
| if (sd.vol()->fatType() == 0) { | if (sd.vol()->fatType() == 0) { | ||||
| cout << pstr("Can't find a valid FAT16/FAT32 partition.\n"); | |||||
| cout << F("Can't find a valid FAT16/FAT32 partition.\n"); | |||||
| reformatMsg(); | reformatMsg(); | ||||
| return; | return; | ||||
| } | |||||
| } | |||||
| if (!sd.vwd()->isOpen()) { | if (!sd.vwd()->isOpen()) { | ||||
| cout << pstr("Can't open root directory.\n"); | |||||
| cout << F("Can't open root directory.\n"); | |||||
| reformatMsg(); | reformatMsg(); | ||||
| return; | return; | ||||
| } | |||||
| cout << pstr("Can't determine error type\n"); | |||||
| } | |||||
| cout << F("Can't determine error type\n"); | |||||
| return; | return; | ||||
| } | } | ||||
| cout << pstr("\nCard successfully initialized.\n"); | |||||
| cout << F("\nCard successfully initialized.\n"); | |||||
| cout << endl; | cout << endl; | ||||
| uint32_t size = sd.card()->cardSize(); | uint32_t size = sd.card()->cardSize(); | ||||
| if (size == 0) { | if (size == 0) { | ||||
| cout << pstr("Can't determine the card size.\n"); | |||||
| cout << F("Can't determine the card size.\n"); | |||||
| cardOrSpeed(); | cardOrSpeed(); | ||||
| return; | return; | ||||
| } | } | ||||
| uint32_t sizeMB = 0.000512 * size + 0.5; | uint32_t sizeMB = 0.000512 * size + 0.5; | ||||
| cout << pstr("Card size: ") << sizeMB; | |||||
| cout << pstr(" MB (MB = 1,000,000 bytes)\n"); | |||||
| cout << F("Card size: ") << sizeMB; | |||||
| cout << F(" MB (MB = 1,000,000 bytes)\n"); | |||||
| cout << endl; | cout << endl; | ||||
| cout << pstr("Volume is FAT") << int(sd.vol()->fatType()); | |||||
| cout << pstr(", Cluster size (bytes): ") << 512L * sd.vol()->blocksPerCluster(); | |||||
| cout << F("Volume is FAT") << int(sd.vol()->fatType()); | |||||
| cout << F(", Cluster size (bytes): ") << 512L * sd.vol()->blocksPerCluster(); | |||||
| cout << endl << endl; | cout << endl << endl; | ||||
| cout << pstr("Files found (name date time size):\n"); | |||||
| cout << F("Files found (date time size name):\n"); | |||||
| sd.ls(LS_R | LS_DATE | LS_SIZE); | sd.ls(LS_R | LS_DATE | LS_SIZE); | ||||
| if ((sizeMB > 1100 && sd.vol()->blocksPerCluster() < 64) | if ((sizeMB > 1100 && sd.vol()->blocksPerCluster() < 64) | ||||
| || (sizeMB < 2200 && sd.vol()->fatType() == 32)) { | |||||
| cout << pstr("\nThis card should be reformatted for best performance.\n"); | |||||
| cout << pstr("Use a cluster size of 32 KB for cards larger than 1 GB.\n"); | |||||
| cout << pstr("Only cards larger than 2 GB should be formatted FAT32.\n"); | |||||
| || (sizeMB < 2200 && sd.vol()->fatType() == 32)) { | |||||
| cout << F("\nThis card should be reformatted for best performance.\n"); | |||||
| cout << F("Use a cluster size of 32 KB for cards larger than 1 GB.\n"); | |||||
| cout << F("Only cards larger than 2 GB should be formatted FAT32.\n"); | |||||
| reformatMsg(); | reformatMsg(); | ||||
| return; | return; | ||||
| } | } | ||||
| // read any existing Serial data | // read any existing Serial data | ||||
| while (Serial.read() >= 0) {} | while (Serial.read() >= 0) {} | ||||
| cout << pstr("\nSuccess! Type any character to restart.\n"); | |||||
| cout << F("\nSuccess! Type any character to restart.\n"); | |||||
| while (Serial.read() < 0) {} | while (Serial.read() < 0) {} | ||||
| } | } |
| /* | /* | ||||
| * This sketch illustrates raw write functions in SdFat that | |||||
| * can be used for high speed data logging. | |||||
| * This program illustrates raw write functions in SdFat that | |||||
| * can be used for high speed data logging. | |||||
| * | * | ||||
| * This sketch simulates logging from a source that produces | |||||
| * This program simulates logging from a source that produces | |||||
| * data at a constant rate of one block every MICROS_PER_BLOCK. | * data at a constant rate of one block every MICROS_PER_BLOCK. | ||||
| * | * | ||||
| * If a high quality SanDisk card is used with this sketch | |||||
| * If a high quality SanDisk card is used with this program | |||||
| * no overruns occur and the maximum block write time is | * no overruns occur and the maximum block write time is | ||||
| * under 2000 micros. | * under 2000 micros. | ||||
| * | * | ||||
| * a few seconds to erase a 500 MB file since the card only | * a few seconds to erase a 500 MB file since the card only | ||||
| * marks the blocks as erased; no data transfer is required. | * marks the blocks as erased; no data transfer is required. | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| #include <SdFatUtil.h> | #include <SdFatUtil.h> | ||||
| void loop(void) { | void loop(void) { | ||||
| while (Serial.read() >= 0) {} | while (Serial.read() >= 0) {} | ||||
| // pstr stores strings in flash to save RAM | // pstr stores strings in flash to save RAM | ||||
| cout << pstr("Type any character to start\n"); | |||||
| cout << F("Type any character to start\n"); | |||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| cout << pstr("Free RAM: ") << FreeRam() << endl; | |||||
| cout << F("Free RAM: ") << FreeRam() << endl; | |||||
| // initialize the SD card at SPI_FULL_SPEED for best performance. | // initialize the SD card at SPI_FULL_SPEED for best performance. | ||||
| // try SPI_HALF_SPEED if bus errors occur. | // try SPI_HALF_SPEED if bus errors occur. | ||||
| if (!sd.begin(chipSelect, SPI_FULL_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_FULL_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // delete possible existing file | // delete possible existing file | ||||
| sd.remove("RAW.TXT"); | |||||
| sd.remove("RawWrite.txt"); | |||||
| // create a contiguous file | // create a contiguous file | ||||
| if (!file.createContiguous(sd.vwd(), "RAW.TXT", 512UL*BLOCK_COUNT)) { | |||||
| if (!file.createContiguous(sd.vwd(), "RawWrite.txt", 512UL*BLOCK_COUNT)) { | |||||
| error("createContiguous failed"); | error("createContiguous failed"); | ||||
| } | } | ||||
| // get the location of the file's blocks | // get the location of the file's blocks | ||||
| pCache[i + 63] = '\n'; | pCache[i + 63] = '\n'; | ||||
| } | } | ||||
| cout << pstr("Start raw write of ") << file.fileSize() << pstr(" bytes at\n"); | |||||
| cout << 512000000UL/MICROS_PER_BLOCK << pstr(" bytes per second\n"); | |||||
| cout << pstr("Please wait ") << (BLOCK_COUNT*MICROS_PER_BLOCK)/1000000UL; | |||||
| cout << pstr(" seconds\n"); | |||||
| cout << F("Start raw write of ") << file.fileSize() << F(" bytes at\n"); | |||||
| cout << 512000000UL/MICROS_PER_BLOCK << F(" bytes per second\n"); | |||||
| cout << F("Please wait ") << (BLOCK_COUNT*MICROS_PER_BLOCK)/1000000UL; | |||||
| cout << F(" seconds\n"); | |||||
| // tell card to setup for multiple block write with pre-erase | // tell card to setup for multiple block write with pre-erase | ||||
| if (!sd.card()->writeStart(bgnBlock, BLOCK_COUNT)) { | if (!sd.card()->writeStart(bgnBlock, BLOCK_COUNT)) { | ||||
| // put block number at start of first line in block | // put block number at start of first line in block | ||||
| uint32_t n = b; | uint32_t n = b; | ||||
| for (int8_t d = 5; d >= 0; d--){ | |||||
| for (int8_t d = 5; d >= 0; d--) { | |||||
| pCache[d] = n || d == 5 ? n % 10 + '0' : ' '; | pCache[d] = n || d == 5 ? n % 10 + '0' : ' '; | ||||
| n /= 10; | n /= 10; | ||||
| } | } | ||||
| // write a 512 byte block | // write a 512 byte block | ||||
| uint32_t tw = micros(); | uint32_t tw = micros(); | ||||
| if (!sd.card()->writeData(pCache)) error("writeData failed"); | |||||
| if (!sd.card()->writeData(pCache)) { | |||||
| error("writeData failed"); | |||||
| } | |||||
| tw = micros() - tw; | tw = micros() - tw; | ||||
| // check for max write time | // check for max write time | ||||
| overruns++; | overruns++; | ||||
| // advance time to reflect overrun | // advance time to reflect overrun | ||||
| tNext = micros(); | tNext = micros(); | ||||
| } | |||||
| else { | |||||
| } else { | |||||
| // wait for time to write next block | // wait for time to write next block | ||||
| while(micros() < tNext); | while(micros() < tNext); | ||||
| } | } | ||||
| t = micros() - t; | t = micros() - t; | ||||
| // end multiple block write mode | // end multiple block write mode | ||||
| if (!sd.card()->writeStop()) error("writeStop failed"); | |||||
| if (!sd.card()->writeStop()) { | |||||
| error("writeStop failed"); | |||||
| } | |||||
| cout << pstr("Done\n"); | |||||
| cout << pstr("Elapsed time: ") << setprecision(3)<< 1.e-6*t; | |||||
| cout << pstr(" seconds\n"); | |||||
| cout << pstr("Max write time: ") << maxWriteTime << pstr(" micros\n"); | |||||
| cout << pstr("Overruns: ") << overruns << endl; | |||||
| cout << F("Done\n"); | |||||
| cout << F("Elapsed time: ") << setprecision(3)<< 1.e-6*t; | |||||
| cout << F(" seconds\n"); | |||||
| cout << F("Max write time: ") << maxWriteTime << F(" micros\n"); | |||||
| cout << F("Overruns: ") << overruns << endl; | |||||
| if (overruns) { | if (overruns) { | ||||
| uint8_t n = overruns > OVER_DIM ? OVER_DIM : overruns; | uint8_t n = overruns > OVER_DIM ? OVER_DIM : overruns; | ||||
| cout << pstr("fileBlock,micros") << endl; | |||||
| cout << F("fileBlock,micros") << endl; | |||||
| for (uint8_t i = 0; i < n; i++) { | for (uint8_t i = 0; i < n; i++) { | ||||
| cout << over[i].block << ',' << over[i].micros << endl; | cout << over[i].block << ',' << over[i].micros << endl; | ||||
| } | } |
| /* | |||||
| SD card read/write | |||||
| This example shows how to read and write data to and from an SD card file | |||||
| The circuit: | |||||
| * SD card attached to SPI bus as follows: | |||||
| ** MOSI - pin 11 | |||||
| ** MISO - pin 12 | |||||
| ** CLK - pin 13 | |||||
| ** CS - pin 4 | |||||
| created Nov 2010 | |||||
| by David A. Mellis | |||||
| modified 9 Apr 2012 | |||||
| by Tom Igoe | |||||
| This example code is in the public domain. | |||||
| */ | |||||
| #define SD_CS_PIN SS | |||||
| #include <SPI.h> | |||||
| //#include <SD.h> | |||||
| #include <SdFat.h> | |||||
| SdFat SD; | |||||
| File myFile; | |||||
| void setup() | |||||
| { | |||||
| // Open serial communications and wait for port to open: | |||||
| Serial.begin(9600); | |||||
| while (!Serial) { | |||||
| ; // wait for serial port to connect. Needed for Leonardo only | |||||
| } | |||||
| Serial.print("Initializing SD card..."); | |||||
| // On the Ethernet Shield, CS is pin 4. It's set as an output by default. | |||||
| // Note that even if it's not used as the CS pin, the hardware SS pin | |||||
| // (10 on most Arduino boards, 53 on the Mega) must be left as an output | |||||
| // or the SD library functions will not work. | |||||
| pinMode(10, OUTPUT); | |||||
| if (!SD.begin(SD_CS_PIN)) { | |||||
| 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. | |||||
| myFile = SD.open("test.txt", FILE_WRITE); | |||||
| // if the file opened okay, write to it: | |||||
| if (myFile) { | |||||
| Serial.print("Writing to test.txt..."); | |||||
| myFile.println("testing 1, 2, 3."); | |||||
| // close the file: | |||||
| myFile.close(); | |||||
| Serial.println("done."); | |||||
| } else { | |||||
| // if the file didn't open, print an error: | |||||
| Serial.println("error opening test.txt"); | |||||
| } | |||||
| // re-open the file for reading: | |||||
| myFile = SD.open("test.txt"); | |||||
| if (myFile) { | |||||
| Serial.println("test.txt:"); | |||||
| // read from the file until there's nothing else in it: | |||||
| while (myFile.available()) { | |||||
| Serial.write(myFile.read()); | |||||
| } | |||||
| // close the file: | |||||
| myFile.close(); | |||||
| } else { | |||||
| // if the file didn't open, print an error: | |||||
| Serial.println("error opening test.txt"); | |||||
| } | |||||
| } | |||||
| void loop() | |||||
| { | |||||
| // nothing happens after setup | |||||
| } | |||||
| const int chipSelect = 4; | const int chipSelect = 4; | ||||
| /* | /* | ||||
| SD card read/write | SD card read/write | ||||
| This example shows how to read and write data to and from an SD card file | |||||
| This example shows how to read and write data to and from an SD card file | |||||
| The circuit: | The circuit: | ||||
| * SD card attached to SPI bus as follows: | * SD card attached to SPI bus as follows: | ||||
| ** MOSI - pin 11 | ** MOSI - pin 11 | ||||
| ** MISO - pin 12 | ** MISO - pin 12 | ||||
| ** CLK - pin 13 | ** CLK - pin 13 | ||||
| ** CS - pin 4 | ** CS - pin 4 | ||||
| created Nov 2010 | created Nov 2010 | ||||
| by David A. Mellis | by David A. Mellis | ||||
| updated 2 Dec 2010 | updated 2 Dec 2010 | ||||
| by Tom Igoe | by Tom Igoe | ||||
| modified by Bill Greiman 11 Apr 2011 | modified by Bill Greiman 11 Apr 2011 | ||||
| This example code is in the public domain. | This example code is in the public domain. | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| SdFat sd; | SdFat sd; | ||||
| SdFile myFile; | SdFile myFile; | ||||
| Serial.println("Type any character to start"); | Serial.println("Type any character to start"); | ||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| // Initialize SdFat or print a detailed error message and halt | // Initialize SdFat or print a detailed error message and halt | ||||
| // Use half speed like the native library. | // Use half speed like the native library. | ||||
| // change to SPI_FULL_SPEED for more performance. | // change to SPI_FULL_SPEED for more performance. | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // open the file for write at end like the Native SD library | // open the file for write at end like the Native SD library | ||||
| if (!myFile.open("test.txt", O_RDWR | O_CREAT | O_AT_END)) { | if (!myFile.open("test.txt", O_RDWR | O_CREAT | O_AT_END)) { | ||||
| // read from the file until there's nothing else in it: | // read from the file until there's nothing else in it: | ||||
| int data; | int data; | ||||
| while ((data = myFile.read()) >= 0) Serial.write(data); | |||||
| while ((data = myFile.read()) >= 0) { | |||||
| Serial.write(data); | |||||
| } | |||||
| // close the file: | // close the file: | ||||
| myFile.close(); | myFile.close(); | ||||
| } | } |
| /* | /* | ||||
| * This sketch will format an SD or SDHC card. | |||||
| * This program will format an SD or SDHC card. | |||||
| * Warning all data will be deleted! | * Warning all data will be deleted! | ||||
| * | * | ||||
| * For SD/SDHC cards larger than 64 MB this | * For SD/SDHC cards larger than 64 MB this | ||||
| * sketch attempts to match the format | |||||
| * program attempts to match the format | |||||
| * generated by SDFormatter available here: | * generated by SDFormatter available here: | ||||
| * | * | ||||
| * http://www.sdcard.org/consumers/formatter/ | * http://www.sdcard.org/consumers/formatter/ | ||||
| * | * | ||||
| * For smaller cards this sketch uses FAT16 | |||||
| * For smaller cards this program uses FAT16 | |||||
| * and SDFormatter uses FAT12. | * and SDFormatter uses FAT12. | ||||
| */ | */ | ||||
| // Print extra info for debug if DEBUG_PRINT is nonzero | // Print extra info for debug if DEBUG_PRINT is nonzero | ||||
| #define DEBUG_PRINT 0 | #define DEBUG_PRINT 0 | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| #if DEBUG_PRINT | #if DEBUG_PRINT | ||||
| #include <SdFatUtil.h> | #include <SdFatUtil.h> | ||||
| char fat16str[] = "FAT16 "; | char fat16str[] = "FAT16 "; | ||||
| char fat32str[] = "FAT32 "; | char fat32str[] = "FAT32 "; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| #define sdError(msg) sdError_P(PSTR(msg)) | |||||
| #define sdError(msg) sdError_F(F(msg)) | |||||
| void sdError_P(const char* str) { | |||||
| cout << pstr("error: "); | |||||
| cout << pgm(str) << endl; | |||||
| void sdError_F(const __FlashStringHelper* str) { | |||||
| cout << F("error: "); | |||||
| cout << str << endl; | |||||
| if (card.errorCode()) { | if (card.errorCode()) { | ||||
| cout << pstr("SD error: ") << hex << int(card.errorCode()); | |||||
| cout << F("SD error: ") << hex << int(card.errorCode()); | |||||
| cout << ',' << int(card.errorData()) << dec << endl; | cout << ',' << int(card.errorData()) << dec << endl; | ||||
| } | } | ||||
| while (1); | while (1); | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| #if DEBUG_PRINT | #if DEBUG_PRINT | ||||
| void debugPrint() { | void debugPrint() { | ||||
| cout << pstr("FreeRam: ") << FreeRam() << endl; | |||||
| cout << pstr("partStart: ") << relSector << endl; | |||||
| cout << pstr("partSize: ") << partSize << endl; | |||||
| cout << pstr("reserved: ") << reservedSectors << endl; | |||||
| cout << pstr("fatStart: ") << fatStart << endl; | |||||
| cout << pstr("fatSize: ") << fatSize << endl; | |||||
| cout << pstr("dataStart: ") << dataStart << endl; | |||||
| cout << pstr("clusterCount: "); | |||||
| cout << F("FreeRam: ") << FreeRam() << endl; | |||||
| cout << F("partStart: ") << relSector << endl; | |||||
| cout << F("partSize: ") << partSize << endl; | |||||
| cout << F("reserved: ") << reservedSectors << endl; | |||||
| cout << F("fatStart: ") << fatStart << endl; | |||||
| cout << F("fatSize: ") << fatSize << endl; | |||||
| cout << F("dataStart: ") << dataStart << endl; | |||||
| cout << F("clusterCount: "); | |||||
| cout << ((relSector + partSize - dataStart)/sectorsPerCluster) << endl; | cout << ((relSector + partSize - dataStart)/sectorsPerCluster) << endl; | ||||
| cout << endl; | cout << endl; | ||||
| cout << pstr("Heads: ") << int(numberOfHeads) << endl; | |||||
| cout << pstr("Sectors: ") << int(sectorsPerTrack) << endl; | |||||
| cout << pstr("Cylinders: "); | |||||
| cout << F("Heads: ") << int(numberOfHeads) << endl; | |||||
| cout << F("Sectors: ") << int(sectorsPerTrack) << endl; | |||||
| cout << F("Cylinders: "); | |||||
| cout << cardSizeBlocks/(numberOfHeads*sectorsPerTrack) << endl; | cout << cardSizeBlocks/(numberOfHeads*sectorsPerTrack) << endl; | ||||
| } | } | ||||
| #endif // DEBUG_PRINT | #endif // DEBUG_PRINT | ||||
| sectorsPerCluster = 128; | sectorsPerCluster = 128; | ||||
| } | } | ||||
| cout << pstr("Blocks/Cluster: ") << int(sectorsPerCluster) << endl; | |||||
| cout << F("Blocks/Cluster: ") << int(sectorsPerCluster) << endl; | |||||
| // set fake disk geometry | // set fake disk geometry | ||||
| sectorsPerTrack = cardCapacityMB <= 256 ? 32 : 63; | sectorsPerTrack = cardCapacityMB <= 256 ? 32 : 63; | ||||
| sdError("Clear FAT/DIR writeStart failed"); | sdError("Clear FAT/DIR writeStart failed"); | ||||
| } | } | ||||
| for (uint32_t i = 0; i < count; i++) { | for (uint32_t i = 0; i < count; i++) { | ||||
| if ((i & 0XFF) == 0) cout << '.'; | |||||
| if ((i & 0XFF) == 0) { | |||||
| cout << '.'; | |||||
| } | |||||
| if (!card.writeData(cache.data)) { | if (!card.writeData(cache.data)) { | ||||
| sdError("Clear FAT/DIR writeData failed"); | sdError("Clear FAT/DIR writeData failed"); | ||||
| } | } | ||||
| part_t* p = cache.mbr.part; | part_t* p = cache.mbr.part; | ||||
| p->boot = 0; | p->boot = 0; | ||||
| uint16_t c = lbnToCylinder(relSector); | uint16_t c = lbnToCylinder(relSector); | ||||
| if (c > 1023) sdError("MBR CHS"); | |||||
| if (c > 1023) { | |||||
| sdError("MBR CHS"); | |||||
| } | |||||
| p->beginCylinderHigh = c >> 8; | p->beginCylinderHigh = c >> 8; | ||||
| p->beginCylinderLow = c & 0XFF; | p->beginCylinderLow = c & 0XFF; | ||||
| p->beginHead = lbnToHead(relSector); | p->beginHead = lbnToHead(relSector); | ||||
| } | } | ||||
| p->firstSector = relSector; | p->firstSector = relSector; | ||||
| p->totalSectors = partSize; | p->totalSectors = partSize; | ||||
| if (!writeCache(0)) sdError("write MBR"); | |||||
| if (!writeCache(0)) { | |||||
| sdError("write MBR"); | |||||
| } | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // generate serial number from card size and micros since boot | // generate serial number from card size and micros since boot | ||||
| nc = (cardSizeBlocks - dataStart)/sectorsPerCluster; | nc = (cardSizeBlocks - dataStart)/sectorsPerCluster; | ||||
| fatSize = (nc + 2 + 255)/256; | fatSize = (nc + 2 + 255)/256; | ||||
| uint32_t r = BU16 + 1 + 2 * fatSize + 32; | uint32_t r = BU16 + 1 + 2 * fatSize + 32; | ||||
| if (dataStart < r) continue; | |||||
| if (dataStart < r) { | |||||
| continue; | |||||
| } | |||||
| relSector = dataStart - r + BU16; | relSector = dataStart - r + BU16; | ||||
| break; | break; | ||||
| } | } | ||||
| // check valid cluster count for FAT16 volume | // check valid cluster count for FAT16 volume | ||||
| if (nc < 4085 || nc >= 65525) sdError("Bad cluster count"); | |||||
| if (nc < 4085 || nc >= 65525) { | |||||
| sdError("Bad cluster count"); | |||||
| } | |||||
| reservedSectors = 1; | reservedSectors = 1; | ||||
| fatStart = relSector + reservedSectors; | fatStart = relSector + reservedSectors; | ||||
| partSize = nc * sectorsPerCluster + 2 * fatSize + reservedSectors + 32; | partSize = nc * sectorsPerCluster + 2 * fatSize + reservedSectors + 32; | ||||
| cache.fat16[1] = 0XFFFF; | cache.fat16[1] = 0XFFFF; | ||||
| // write first block of FAT and backup for reserved clusters | // write first block of FAT and backup for reserved clusters | ||||
| if (!writeCache(fatStart) | if (!writeCache(fatStart) | ||||
| || !writeCache(fatStart + fatSize)) { | |||||
| || !writeCache(fatStart + fatSize)) { | |||||
| sdError("FAT16 reserve failed"); | sdError("FAT16 reserve failed"); | ||||
| } | } | ||||
| } | } | ||||
| nc = (cardSizeBlocks - dataStart)/sectorsPerCluster; | nc = (cardSizeBlocks - dataStart)/sectorsPerCluster; | ||||
| fatSize = (nc + 2 + 127)/128; | fatSize = (nc + 2 + 127)/128; | ||||
| uint32_t r = relSector + 9 + 2 * fatSize; | uint32_t r = relSector + 9 + 2 * fatSize; | ||||
| if (dataStart >= r) break; | |||||
| if (dataStart >= r) { | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| // error if too few clusters in FAT32 volume | // error if too few clusters in FAT32 volume | ||||
| if (nc < 65525) sdError("Bad cluster count"); | |||||
| if (nc < 65525) { | |||||
| sdError("Bad cluster count"); | |||||
| } | |||||
| reservedSectors = dataStart - relSector - 2 * fatSize; | reservedSectors = dataStart - relSector - 2 * fatSize; | ||||
| fatStart = relSector + reservedSectors; | fatStart = relSector + reservedSectors; | ||||
| partSize = nc * sectorsPerCluster + dataStart - relSector; | partSize = nc * sectorsPerCluster + dataStart - relSector; | ||||
| memcpy(pb->fileSystemType, fat32str, sizeof(pb->fileSystemType)); | memcpy(pb->fileSystemType, fat32str, sizeof(pb->fileSystemType)); | ||||
| // write partition boot sector and backup | // write partition boot sector and backup | ||||
| if (!writeCache(relSector) | if (!writeCache(relSector) | ||||
| || !writeCache(relSector + 6)) { | |||||
| || !writeCache(relSector + 6)) { | |||||
| sdError("FAT32 write PBS failed"); | sdError("FAT32 write PBS failed"); | ||||
| } | } | ||||
| clearCache(true); | clearCache(true); | ||||
| // write extra boot area and backup | // write extra boot area and backup | ||||
| if (!writeCache(relSector + 2) | if (!writeCache(relSector + 2) | ||||
| || !writeCache(relSector + 8)) { | |||||
| || !writeCache(relSector + 8)) { | |||||
| sdError("FAT32 PBS ext failed"); | sdError("FAT32 PBS ext failed"); | ||||
| } | } | ||||
| fat32_fsinfo_t* pf = &cache.fsinfo; | fat32_fsinfo_t* pf = &cache.fsinfo; | ||||
| pf->nextFree = 0XFFFFFFFF; | pf->nextFree = 0XFFFFFFFF; | ||||
| // write FSINFO sector and backup | // write FSINFO sector and backup | ||||
| if (!writeCache(relSector + 1) | if (!writeCache(relSector + 1) | ||||
| || !writeCache(relSector + 7)) { | |||||
| || !writeCache(relSector + 7)) { | |||||
| sdError("FAT32 FSINFO failed"); | sdError("FAT32 FSINFO failed"); | ||||
| } | } | ||||
| clearFatDir(fatStart, 2 * fatSize + sectorsPerCluster); | clearFatDir(fatStart, 2 * fatSize + sectorsPerCluster); | ||||
| cache.fat32[2] = 0x0FFFFFFF; | cache.fat32[2] = 0x0FFFFFFF; | ||||
| // write first block of FAT and backup for reserved clusters | // write first block of FAT and backup for reserved clusters | ||||
| if (!writeCache(fatStart) | if (!writeCache(fatStart) | ||||
| || !writeCache(fatStart + fatSize)) { | |||||
| || !writeCache(fatStart + fatSize)) { | |||||
| sdError("FAT32 reserve failed"); | sdError("FAT32 reserve failed"); | ||||
| } | } | ||||
| } | } | ||||
| // flash erase all data | // flash erase all data | ||||
| uint32_t const ERASE_SIZE = 262144L; | uint32_t const ERASE_SIZE = 262144L; | ||||
| void eraseCard() { | void eraseCard() { | ||||
| cout << endl << pstr("Erasing\n"); | |||||
| cout << endl << F("Erasing\n"); | |||||
| uint32_t firstBlock = 0; | uint32_t firstBlock = 0; | ||||
| uint32_t lastBlock; | uint32_t lastBlock; | ||||
| uint16_t n = 0; | uint16_t n = 0; | ||||
| do { | do { | ||||
| lastBlock = firstBlock + ERASE_SIZE - 1; | lastBlock = firstBlock + ERASE_SIZE - 1; | ||||
| if (lastBlock >= cardSizeBlocks) lastBlock = cardSizeBlocks - 1; | |||||
| if (!card.erase(firstBlock, lastBlock)) sdError("erase failed"); | |||||
| if (lastBlock >= cardSizeBlocks) { | |||||
| lastBlock = cardSizeBlocks - 1; | |||||
| } | |||||
| if (!card.erase(firstBlock, lastBlock)) { | |||||
| sdError("erase failed"); | |||||
| } | |||||
| cout << '.'; | cout << '.'; | ||||
| if ((n++)%32 == 31) cout << endl; | |||||
| if ((n++)%32 == 31) { | |||||
| cout << endl; | |||||
| } | |||||
| firstBlock += ERASE_SIZE; | firstBlock += ERASE_SIZE; | ||||
| } while (firstBlock < cardSizeBlocks); | } while (firstBlock < cardSizeBlocks); | ||||
| cout << endl; | cout << endl; | ||||
| if (!card.readBlock(0, cache.data)) sdError("readBlock"); | |||||
| if (!card.readBlock(0, cache.data)) { | |||||
| sdError("readBlock"); | |||||
| } | |||||
| cout << hex << showbase << setfill('0') << internal; | cout << hex << showbase << setfill('0') << internal; | ||||
| cout << pstr("All data set to ") << setw(4) << int(cache.data[0]) << endl; | |||||
| cout << F("All data set to ") << setw(4) << int(cache.data[0]) << endl; | |||||
| cout << dec << noshowbase << setfill(' ') << right; | cout << dec << noshowbase << setfill(' ') << right; | ||||
| cout << pstr("Erase done\n"); | |||||
| cout << F("Erase done\n"); | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void formatCard() { | void formatCard() { | ||||
| cout << endl; | cout << endl; | ||||
| cout << pstr("Formatting\n"); | |||||
| cout << F("Formatting\n"); | |||||
| initSizes(); | initSizes(); | ||||
| if (card.type() != SD_CARD_TYPE_SDHC) { | if (card.type() != SD_CARD_TYPE_SDHC) { | ||||
| cout << pstr("FAT16\n"); | |||||
| cout << F("FAT16\n"); | |||||
| makeFat16(); | makeFat16(); | ||||
| } else { | } else { | ||||
| cout << pstr("FAT32\n"); | |||||
| cout << F("FAT32\n"); | |||||
| makeFat32(); | makeFat32(); | ||||
| } | } | ||||
| #if DEBUG_PRINT | #if DEBUG_PRINT | ||||
| debugPrint(); | debugPrint(); | ||||
| #endif // DEBUG_PRINT | #endif // DEBUG_PRINT | ||||
| cout << pstr("Format done\n"); | |||||
| cout << F("Format done\n"); | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void setup() { | void setup() { | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| cout << pstr( | |||||
| "\n" | |||||
| "This sketch can erase and/or format SD/SDHC cards.\n" | |||||
| "\n" | |||||
| "Erase uses the card's fast flash erase command.\n" | |||||
| "Flash erase sets all data to 0X00 for most cards\n" | |||||
| "and 0XFF for a few vendor's cards.\n" | |||||
| "\n" | |||||
| "Cards larger than 2 GB will be formatted FAT32 and\n" | |||||
| "smaller cards will be formatted FAT16.\n" | |||||
| "\n" | |||||
| "Warning, all data on the card will be erased.\n" | |||||
| "Enter 'Y' to continue: "); | |||||
| cout << F( | |||||
| "\n" | |||||
| "This program can erase and/or format SD/SDHC cards.\n" | |||||
| "\n" | |||||
| "Erase uses the card's fast flash erase command.\n" | |||||
| "Flash erase sets all data to 0X00 for most cards\n" | |||||
| "and 0XFF for a few vendor's cards.\n" | |||||
| "\n" | |||||
| "Cards larger than 2 GB will be formatted FAT32 and\n" | |||||
| "smaller cards will be formatted FAT16.\n" | |||||
| "\n" | |||||
| "Warning, all data on the card will be erased.\n" | |||||
| "Enter 'Y' to continue: "); | |||||
| while (!Serial.available()) {} | while (!Serial.available()) {} | ||||
| delay(400); // catch Due restart problem | delay(400); // catch Due restart problem | ||||
| c = Serial.read(); | c = Serial.read(); | ||||
| cout << c << endl; | cout << c << endl; | ||||
| if (c != 'Y') { | if (c != 'Y') { | ||||
| cout << pstr("Quiting, you did not enter 'Y'.\n"); | |||||
| cout << F("Quiting, you did not enter 'Y'.\n"); | |||||
| return; | return; | ||||
| } | } | ||||
| // read any existing Serial data | // read any existing Serial data | ||||
| while (Serial.read() >= 0) {} | while (Serial.read() >= 0) {} | ||||
| cout << pstr( | |||||
| "\n" | |||||
| "Options are:\n" | |||||
| "E - erase the card and skip formatting.\n" | |||||
| "F - erase and then format the card. (recommended)\n" | |||||
| "Q - quick format the card without erase.\n" | |||||
| "\n" | |||||
| "Enter option: "); | |||||
| cout << F( | |||||
| "\n" | |||||
| "Options are:\n" | |||||
| "E - erase the card and skip formatting.\n" | |||||
| "F - erase and then format the card. (recommended)\n" | |||||
| "Q - quick format the card without erase.\n" | |||||
| "\n" | |||||
| "Enter option: "); | |||||
| while (!Serial.available()) {} | while (!Serial.available()) {} | ||||
| c = Serial.read(); | c = Serial.read(); | ||||
| cout << c << endl; | cout << c << endl; | ||||
| if (!strchr("EFQ", c)) { | if (!strchr("EFQ", c)) { | ||||
| cout << pstr("Quiting, invalid option entered.") << endl; | |||||
| cout << F("Quiting, invalid option entered.") << endl; | |||||
| return; | return; | ||||
| } | } | ||||
| if (!card.begin(chipSelect, spiSpeed)) { | if (!card.begin(chipSelect, spiSpeed)) { | ||||
| cout << pstr( | |||||
| "\nSD initialization failure!\n" | |||||
| "Is the SD card inserted correctly?\n" | |||||
| "Is chip select correct at the top of this sketch?\n"); | |||||
| cout << F( | |||||
| "\nSD initialization failure!\n" | |||||
| "Is the SD card inserted correctly?\n" | |||||
| "Is chip select correct at the top of this program?\n"); | |||||
| sdError("card.begin failed"); | sdError("card.begin failed"); | ||||
| } | } | ||||
| cardSizeBlocks = card.cardSize(); | cardSizeBlocks = card.cardSize(); | ||||
| if (cardSizeBlocks == 0) sdError("cardSize"); | |||||
| if (cardSizeBlocks == 0) { | |||||
| sdError("cardSize"); | |||||
| } | |||||
| cardCapacityMB = (cardSizeBlocks + 2047)/2048; | cardCapacityMB = (cardSizeBlocks + 2047)/2048; | ||||
| cout << pstr("Card Size: ") << cardCapacityMB; | |||||
| cout << pstr(" MB, (MB = 1,048,576 bytes)") << endl; | |||||
| cout << F("Card Size: ") << cardCapacityMB; | |||||
| cout << F(" MB, (MB = 1,048,576 bytes)") << endl; | |||||
| if (c == 'E' || c == 'F') { | if (c == 'E' || c == 'F') { | ||||
| eraseCard(); | eraseCard(); |
| /* | /* | ||||
| * This sketch attempts to initialize an SD card and analyze its structure. | |||||
| * This program attempts to initialize an SD card and analyze its structure. | |||||
| */ | */ | ||||
| #include <SPI.h> | #include <SPI.h> | ||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| uint32_t eraseSize; | uint32_t eraseSize; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // store error strings in flash | // store error strings in flash | ||||
| #define sdErrorMsg(msg) sdErrorMsg_P(PSTR(msg)); | |||||
| void sdErrorMsg_P(const char* str) { | |||||
| cout << pgm(str) << endl; | |||||
| #define sdErrorMsg(msg) sdErrorMsg_F(F(msg)); | |||||
| void sdErrorMsg_F(const __FlashStringHelper* str) { | |||||
| cout << str << endl; | |||||
| if (sd.card()->errorCode()) { | if (sd.card()->errorCode()) { | ||||
| cout << pstr("SD errorCode: "); | |||||
| cout << F("SD errorCode: "); | |||||
| cout << hex << int(sd.card()->errorCode()) << endl; | cout << hex << int(sd.card()->errorCode()) << endl; | ||||
| cout << pstr("SD errorData: "); | |||||
| cout << F("SD errorData: "); | |||||
| cout << int(sd.card()->errorData()) << dec << endl; | cout << int(sd.card()->errorData()) << dec << endl; | ||||
| } | } | ||||
| } | } | ||||
| sdErrorMsg("readCID failed"); | sdErrorMsg("readCID failed"); | ||||
| return false; | return false; | ||||
| } | } | ||||
| cout << pstr("\nManufacturer ID: "); | |||||
| cout << F("\nManufacturer ID: "); | |||||
| cout << hex << int(cid.mid) << dec << endl; | cout << hex << int(cid.mid) << dec << endl; | ||||
| cout << pstr("OEM ID: ") << cid.oid[0] << cid.oid[1] << endl; | |||||
| cout << pstr("Product: "); | |||||
| cout << F("OEM ID: ") << cid.oid[0] << cid.oid[1] << endl; | |||||
| cout << F("Product: "); | |||||
| for (uint8_t i = 0; i < 5; i++) { | for (uint8_t i = 0; i < 5; i++) { | ||||
| cout << cid.pnm[i]; | cout << cid.pnm[i]; | ||||
| } | } | ||||
| cout << pstr("\nVersion: "); | |||||
| cout << F("\nVersion: "); | |||||
| cout << int(cid.prv_n) << '.' << int(cid.prv_m) << endl; | cout << int(cid.prv_n) << '.' << int(cid.prv_m) << endl; | ||||
| cout << pstr("Serial number: ") << hex << cid.psn << dec << endl; | |||||
| cout << pstr("Manufacturing date: "); | |||||
| cout << F("Serial number: ") << hex << cid.psn << dec << endl; | |||||
| cout << F("Manufacturing date: "); | |||||
| cout << int(cid.mdt_month) << '/'; | cout << int(cid.mdt_month) << '/'; | ||||
| cout << (2000 + cid.mdt_year_low + 10 * cid.mdt_year_high) << endl; | cout << (2000 + cid.mdt_year_low + 10 * cid.mdt_year_high) << endl; | ||||
| cout << endl; | cout << endl; | ||||
| eraseSingleBlock = csd.v2.erase_blk_en; | eraseSingleBlock = csd.v2.erase_blk_en; | ||||
| eraseSize = (csd.v2.sector_size_high << 1) | csd.v2.sector_size_low; | eraseSize = (csd.v2.sector_size_high << 1) | csd.v2.sector_size_low; | ||||
| } else { | } else { | ||||
| cout << pstr("csd version error\n"); | |||||
| cout << F("csd version error\n"); | |||||
| return false; | return false; | ||||
| } | } | ||||
| eraseSize++; | eraseSize++; | ||||
| cout << pstr("cardSize: ") << 0.000512*cardSize; | |||||
| cout << pstr(" MB (MB = 1,000,000 bytes)\n"); | |||||
| cout << F("cardSize: ") << 0.000512*cardSize; | |||||
| cout << F(" MB (MB = 1,000,000 bytes)\n"); | |||||
| cout << pstr("flashEraseSize: ") << int(eraseSize) << pstr(" blocks\n"); | |||||
| cout << pstr("eraseSingleBlock: "); | |||||
| cout << F("flashEraseSize: ") << int(eraseSize) << F(" blocks\n"); | |||||
| cout << F("eraseSingleBlock: "); | |||||
| if (eraseSingleBlock) { | if (eraseSingleBlock) { | ||||
| cout << pstr("true\n"); | |||||
| cout << F("true\n"); | |||||
| } else { | } else { | ||||
| cout << pstr("false\n"); | |||||
| cout << F("false\n"); | |||||
| } | } | ||||
| return true; | return true; | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| if (!sd.card()->readBlock(0, p->data)) { | if (!sd.card()->readBlock(0, p->data)) { | ||||
| sdErrorMsg("read MBR failed"); | |||||
| return false; | |||||
| sdErrorMsg("read MBR failed"); | |||||
| return false; | |||||
| } | } | ||||
| for (uint8_t ip = 1; ip < 5; ip++) { | for (uint8_t ip = 1; ip < 5; ip++) { | ||||
| part_t *pt = &p->mbr.part[ip - 1]; | part_t *pt = &p->mbr.part[ip - 1]; | ||||
| if ((pt->boot & 0X7F) != 0 || pt->firstSector > cardSize) { | if ((pt->boot & 0X7F) != 0 || pt->firstSector > cardSize) { | ||||
| cout << pstr("\nNo MBR. Assuming Super Floppy format.\n"); | |||||
| cout << F("\nNo MBR. Assuming Super Floppy format.\n"); | |||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| cout << pstr("\nSD Partition Table\n"); | |||||
| cout << pstr("part,boot,type,start,length\n"); | |||||
| cout << F("\nSD Partition Table\n"); | |||||
| cout << F("part,boot,type,start,length\n"); | |||||
| for (uint8_t ip = 1; ip < 5; ip++) { | for (uint8_t ip = 1; ip < 5; ip++) { | ||||
| part_t *pt = &p->mbr.part[ip - 1]; | part_t *pt = &p->mbr.part[ip - 1]; | ||||
| cout << int(ip) << ',' << hex << int(pt->boot) << ',' << int(pt->type); | cout << int(ip) << ',' << hex << int(pt->boot) << ',' << int(pt->type); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void volDmp() { | void volDmp() { | ||||
| cout << pstr("\nVolume is FAT") << int(sd.vol()->fatType()) << endl; | |||||
| cout << pstr("blocksPerCluster: ") << int(sd.vol()->blocksPerCluster()) << endl; | |||||
| cout << pstr("clusterCount: ") << sd.vol()->clusterCount() << endl; | |||||
| cout << pstr("freeClusters: "); | |||||
| cout << F("\nVolume is FAT") << int(sd.vol()->fatType()) << endl; | |||||
| cout << F("blocksPerCluster: ") << int(sd.vol()->blocksPerCluster()) << endl; | |||||
| cout << F("clusterCount: ") << sd.vol()->clusterCount() << endl; | |||||
| cout << F("freeClusters: "); | |||||
| uint32_t volFree = sd.vol()->freeClusterCount(); | uint32_t volFree = sd.vol()->freeClusterCount(); | ||||
| cout << volFree << endl; | cout << volFree << endl; | ||||
| float fs = 0.000512*volFree*sd.vol()->blocksPerCluster(); | float fs = 0.000512*volFree*sd.vol()->blocksPerCluster(); | ||||
| cout << pstr("freeSpace: ") << fs << pstr(" MB (MB = 1,000,000 bytes)\n"); | |||||
| cout << pstr("fatStartBlock: ") << sd.vol()->fatStartBlock() << endl; | |||||
| cout << pstr("fatCount: ") << int(sd.vol()->fatCount()) << endl; | |||||
| cout << pstr("blocksPerFat: ") << sd.vol()->blocksPerFat() << endl; | |||||
| cout << pstr("rootDirStart: ") << sd.vol()->rootDirStart() << endl; | |||||
| cout << pstr("dataStartBlock: ") << sd.vol()->dataStartBlock() << endl; | |||||
| cout << F("freeSpace: ") << fs << F(" MB (MB = 1,000,000 bytes)\n"); | |||||
| cout << F("fatStartBlock: ") << sd.vol()->fatStartBlock() << endl; | |||||
| cout << F("fatCount: ") << int(sd.vol()->fatCount()) << endl; | |||||
| cout << F("blocksPerFat: ") << sd.vol()->blocksPerFat() << endl; | |||||
| cout << F("rootDirStart: ") << sd.vol()->rootDirStart() << endl; | |||||
| cout << F("dataStartBlock: ") << sd.vol()->dataStartBlock() << endl; | |||||
| if (sd.vol()->dataStartBlock() % eraseSize) { | if (sd.vol()->dataStartBlock() % eraseSize) { | ||||
| cout << pstr("Data area is not aligned on flash erase boundaries!\n"); | |||||
| cout << pstr("Download and use formatter from www.sdsd.card()->org/consumer!\n"); | |||||
| cout << F("Data area is not aligned on flash erase boundaries!\n"); | |||||
| cout << F("Download and use formatter from www.sdsd.card()->org/consumer!\n"); | |||||
| } | } | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| cout << uppercase << showbase << endl; | cout << uppercase << showbase << endl; | ||||
| // pstr stores strings in flash to save RAM | // pstr stores strings in flash to save RAM | ||||
| cout << pstr("SdFat version: ") << SD_FAT_VERSION << endl; | |||||
| cout << F("SdFat version: ") << SD_FAT_VERSION << endl; | |||||
| if (DISABLE_CHIP_SELECT < 0) { | if (DISABLE_CHIP_SELECT < 0) { | ||||
| cout << pstr( | |||||
| "\nAssuming the SD is the only SPI device.\n" | |||||
| "Edit DISABLE_CHIP_SELECT to disable another device.\n"); | |||||
| cout << F( | |||||
| "\nAssuming the SD is the only SPI device.\n" | |||||
| "Edit DISABLE_CHIP_SELECT to disable another device.\n"); | |||||
| } else { | } else { | ||||
| cout << pstr("\nDisabling SPI device on pin "); | |||||
| cout << F("\nDisabling SPI device on pin "); | |||||
| cout << int(DISABLE_CHIP_SELECT) << endl; | cout << int(DISABLE_CHIP_SELECT) << endl; | ||||
| pinMode(DISABLE_CHIP_SELECT, OUTPUT); | pinMode(DISABLE_CHIP_SELECT, OUTPUT); | ||||
| digitalWrite(DISABLE_CHIP_SELECT, HIGH); | digitalWrite(DISABLE_CHIP_SELECT, HIGH); | ||||
| } | } | ||||
| cout << pstr("\nAssuming the SD chip select pin is: ") <<int(SD_CHIP_SELECT); | |||||
| cout << pstr("\nEdit SD_CHIP_SELECT to change the SD chip select pin.\n"); | |||||
| cout << F("\nAssuming the SD chip select pin is: ") <<int(SD_CHIP_SELECT); | |||||
| cout << F("\nEdit SD_CHIP_SELECT to change the SD chip select pin.\n"); | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void loop() { | void loop() { | ||||
| while (Serial.read() >= 0) {} | while (Serial.read() >= 0) {} | ||||
| // pstr stores strings in flash to save RAM | // pstr stores strings in flash to save RAM | ||||
| cout << pstr("\ntype any character to start\n"); | |||||
| cout << F("\ntype any character to start\n"); | |||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| uint32_t t = millis(); | uint32_t t = millis(); | ||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| return; | return; | ||||
| } | } | ||||
| t = millis() - t; | t = millis() - t; | ||||
| cardSize = sd.card()->cardSize(); | cardSize = sd.card()->cardSize(); | ||||
| if (cardSize == 0) { | if (cardSize == 0) { | ||||
| sdErrorMsg("cardSize failed"); | sdErrorMsg("cardSize failed"); | ||||
| return; | return; | ||||
| } | } | ||||
| cout << pstr("\ninit time: ") << t << " ms" << endl; | |||||
| cout << pstr("\nCard type: "); | |||||
| cout << F("\ninit time: ") << t << " ms" << endl; | |||||
| cout << F("\nCard type: "); | |||||
| switch (sd.card()->type()) { | switch (sd.card()->type()) { | ||||
| case SD_CARD_TYPE_SD1: | |||||
| cout << pstr("SD1\n"); | |||||
| break; | |||||
| case SD_CARD_TYPE_SD1: | |||||
| cout << F("SD1\n"); | |||||
| break; | |||||
| case SD_CARD_TYPE_SD2: | |||||
| cout << pstr("SD2\n"); | |||||
| break; | |||||
| case SD_CARD_TYPE_SD2: | |||||
| cout << F("SD2\n"); | |||||
| break; | |||||
| case SD_CARD_TYPE_SDHC: | |||||
| if (cardSize < 70000000) { | |||||
| cout << pstr("SDHC\n"); | |||||
| } else { | |||||
| cout << pstr("SDXC\n"); | |||||
| } | |||||
| break; | |||||
| case SD_CARD_TYPE_SDHC: | |||||
| if (cardSize < 70000000) { | |||||
| cout << F("SDHC\n"); | |||||
| } else { | |||||
| cout << F("SDXC\n"); | |||||
| } | |||||
| break; | |||||
| default: | |||||
| cout << pstr("Unknown\n"); | |||||
| default: | |||||
| cout << F("Unknown\n"); | |||||
| } | |||||
| if (!cidDmp()) { | |||||
| return; | |||||
| } | |||||
| if (!csdDmp()) { | |||||
| return; | |||||
| } | } | ||||
| if (!cidDmp()) return; | |||||
| if (!csdDmp()) return; | |||||
| uint32_t ocr; | uint32_t ocr; | ||||
| if (!sd.card()->readOCR(&ocr)) { | if (!sd.card()->readOCR(&ocr)) { | ||||
| sdErrorMsg("\nreadOCR failed"); | sdErrorMsg("\nreadOCR failed"); | ||||
| return; | |||||
| return; | |||||
| } | |||||
| cout << F("OCR: ") << hex << ocr << dec << endl; | |||||
| if (!partDmp()) { | |||||
| return; | |||||
| } | } | ||||
| cout << pstr("OCR: ") << hex << ocr << dec << endl; | |||||
| if (!partDmp()) return; | |||||
| if (!sd.fsBegin()) { | if (!sd.fsBegin()) { | ||||
| sdErrorMsg("\nFile System initialization failed.\n"); | sdErrorMsg("\nFile System initialization failed.\n"); | ||||
| return; | return; |
| // | // | ||||
| #include <SPI.h> | #include <SPI.h> | ||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| #if USE_MULTIPLE_SPI_TYPES // Must be nonzero in SdFat/SdFatConfig.h | |||||
| #if SD_SPI_CONFIGURATION >= 3 // Must be set in SdFat/SdFatConfig.h | |||||
| // | // | ||||
| // Pin numbers in templates must be constants. | // Pin numbers in templates must be constants. | ||||
| const uint8_t SOFT_MISO_PIN = 12; | const uint8_t SOFT_MISO_PIN = 12; | ||||
| void setup() { | void setup() { | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // Wait for Leonardo | |||||
| while (!Serial) {} // Wait for Leonardo | |||||
| Serial.println("Type any character to start"); | Serial.println("Type any character to start"); | ||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| if (!sd.begin(SD_CHIP_SELECT_PIN)) sd.initErrorHalt(); | |||||
| if (!file.open("SOFT_SPI.TXT", O_CREAT | O_RDWR)) { | |||||
| if (!sd.begin(SD_CHIP_SELECT_PIN)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| if (!file.open("SoftSPI.txt", O_CREAT | O_RDWR)) { | |||||
| sd.errorHalt(F("open failed")); | sd.errorHalt(F("open failed")); | ||||
| } | } | ||||
| file.println(F("This line was printed using software SPI.")); | file.println(F("This line was printed using software SPI.")); | ||||
| file.close(); | file.close(); | ||||
| Serial.println(F("Done.")); | Serial.println(F("Done.")); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void loop() {} | void loop() {} | ||||
| #else // USE_MULTIPLE_SPI_TYPES | |||||
| #error USE_MULTIPLE_SPI_TYPES must be set nonzero in SdFat/SdFatConfig.h | |||||
| #endif //USE_MULTIPLE_SPI_TYPES | |||||
| #else // SD_SPI_CONFIGURATION >= 3 | |||||
| #error SD_SPI_CONFIGURATION must be set to 3 in SdFat/SdFatConfig.h | |||||
| #endif //SD_SPI_CONFIGURATION >= 3 |
| // Benchmark comparing SdFile and StdioStream. | // Benchmark comparing SdFile and StdioStream. | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // Define PRINT_FIELD nonzero to use printField. | // Define PRINT_FIELD nonzero to use printField. | ||||
| float f[100]; | float f[100]; | ||||
| char buf[20]; | char buf[20]; | ||||
| char* label[] = | |||||
| {"uint8_t 0 to 255, 100 times ", "uint16_t 0 to 20000", | |||||
| char* label[] = | |||||
| { "uint8_t 0 to 255, 100 times ", "uint16_t 0 to 20000", | |||||
| "uint32_t 0 to 20000", "uint32_t 1000000000 to 1000010000", | "uint32_t 0 to 20000", "uint32_t 1000000000 to 1000010000", | ||||
| "float nnn.ffff, 10000 times"}; | |||||
| "float nnn.ffff, 10000 times" | |||||
| }; | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void setup() { | void setup() { | ||||
| uint32_t m; | uint32_t m; | ||||
| uint32_t stdioSize; | uint32_t stdioSize; | ||||
| uint32_t printTime; | uint32_t printTime; | ||||
| uint32_t stdioTime; | uint32_t stdioTime; | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} | while (!Serial) {} | ||||
| Serial.println(F("Type any character to start")); | Serial.println(F("Type any character to start")); | ||||
| while (!Serial.available()); | while (!Serial.available()); | ||||
| Serial.println(F("Starting test")); | Serial.println(F("Starting test")); | ||||
| if (!sd.begin(SD_CS_PIN)) sd.errorHalt(); | |||||
| if (!sd.begin(SD_CS_PIN)) { | |||||
| sd.errorHalt(); | |||||
| } | |||||
| for (uint8_t i = 0; i < 100; i++) { | for (uint8_t i = 0; i < 100; i++) { | ||||
| f[i] = 123.0 + 0.1234*i; | f[i] = 123.0 + 0.1234*i; | ||||
| } | |||||
| } | |||||
| for (uint8_t dataType = 0; dataType < 5; dataType++) { | for (uint8_t dataType = 0; dataType < 5; dataType++) { | ||||
| for (uint8_t fileType = 0; fileType < 2; fileType++) { | for (uint8_t fileType = 0; fileType < 2; fileType++) { | ||||
| if (!fileType) { | if (!fileType) { | ||||
| if (!printFile.open("PRRINT.TXT", O_CREAT | O_RDWR | O_TRUNC)) { | |||||
| if (!printFile.open("print.txt", O_CREAT | O_RDWR | O_TRUNC)) { | |||||
| Serial.println("open fail"); | Serial.println("open fail"); | ||||
| return; | |||||
| return; | |||||
| } | } | ||||
| printTime = millis(); | printTime = millis(); | ||||
| switch (dataType) { | switch (dataType) { | ||||
| for (uint8_t j = 0; j < 255; j++) { | for (uint8_t j = 0; j < 255; j++) { | ||||
| printFile.println(j); | printFile.println(j); | ||||
| } | } | ||||
| } | |||||
| } | |||||
| break; | break; | ||||
| case 1: | case 1: | ||||
| for (uint16_t i = 0; i < 20000; i++) { | for (uint16_t i = 0; i < 20000; i++) { | ||||
| printFile.println(i); | printFile.println(i); | ||||
| } | } | ||||
| break; | break; | ||||
| case 2: | case 2: | ||||
| for (uint32_t i = 0; i < 20000; i++) { | for (uint32_t i = 0; i < 20000; i++) { | ||||
| printFile.println(i); | printFile.println(i); | ||||
| } | } | ||||
| break; | break; | ||||
| case 3: | case 3: | ||||
| for (uint16_t i = 0; i < 10000; i++) { | for (uint16_t i = 0; i < 10000; i++) { | ||||
| printFile.println(i + 1000000000UL); | printFile.println(i + 1000000000UL); | ||||
| } | } | ||||
| break; | break; | ||||
| case 4: | case 4: | ||||
| for (int j = 0; j < 100; j++) { | for (int j = 0; j < 100; j++) { | ||||
| for (uint8_t i = 0; i < 100; i++) { | for (uint8_t i = 0; i < 100; i++) { | ||||
| printFile.println(f[i], 4); | printFile.println(f[i], 4); | ||||
| } | } | ||||
| } | } | ||||
| break; | |||||
| break; | |||||
| default: | default: | ||||
| break; | break; | ||||
| } | } | ||||
| printFile.sync(); | |||||
| printFile.sync(); | |||||
| printTime = millis() - printTime; | printTime = millis() - printTime; | ||||
| printFile.rewind(); | printFile.rewind(); | ||||
| printSize = printFile.fileSize(); | |||||
| printSize = printFile.fileSize(); | |||||
| } else { | } else { | ||||
| if (!stdioFile.fopen("STREAM.TXT", "w+")) { | |||||
| if (!stdioFile.fopen("stream.txt", "w+")) { | |||||
| Serial.println("fopen fail"); | Serial.println("fopen fail"); | ||||
| return; | return; | ||||
| } | } | ||||
| stdioTime = millis(); | stdioTime = millis(); | ||||
| switch (dataType) { | |||||
| switch (dataType) { | |||||
| case 0: | case 0: | ||||
| for (uint16_t i =0; i < 100; i++) { | for (uint16_t i =0; i < 100; i++) { | ||||
| for (uint8_t j = 0; j < 255; j++) { | for (uint8_t j = 0; j < 255; j++) { | ||||
| #if PRINT_FIELD | |||||
| #if PRINT_FIELD | |||||
| stdioFile.printField(j, '\n'); | stdioFile.printField(j, '\n'); | ||||
| #else // PRINT_FIELD | |||||
| #else // PRINT_FIELD | |||||
| stdioFile.println(j); | stdioFile.println(j); | ||||
| #endif // PRINT_FIELD | |||||
| #endif // PRINT_FIELD | |||||
| } | } | ||||
| } | |||||
| } | |||||
| break; | break; | ||||
| case 1: | case 1: | ||||
| for (uint16_t i = 0; i < 20000; i++) { | for (uint16_t i = 0; i < 20000; i++) { | ||||
| #if PRINT_FIELD | |||||
| #if PRINT_FIELD | |||||
| stdioFile.printField(i, '\n'); | stdioFile.printField(i, '\n'); | ||||
| #else // PRINT_FIELD | |||||
| #else // PRINT_FIELD | |||||
| stdioFile.println(i); | stdioFile.println(i); | ||||
| #endif // PRINT_FIELD | |||||
| #endif // PRINT_FIELD | |||||
| } | } | ||||
| break; | break; | ||||
| case 2: | case 2: | ||||
| for (uint32_t i = 0; i < 20000; i++) { | for (uint32_t i = 0; i < 20000; i++) { | ||||
| #if PRINT_FIELD | |||||
| #if PRINT_FIELD | |||||
| stdioFile.printField(i, '\n'); | stdioFile.printField(i, '\n'); | ||||
| #else // PRINT_FIELD | |||||
| #else // PRINT_FIELD | |||||
| stdioFile.println(i); | stdioFile.println(i); | ||||
| #endif // PRINT_FIELD | |||||
| #endif // PRINT_FIELD | |||||
| } | } | ||||
| break; | break; | ||||
| case 3: | case 3: | ||||
| for (uint16_t i = 0; i < 10000; i++) { | for (uint16_t i = 0; i < 10000; i++) { | ||||
| #if PRINT_FIELD | |||||
| #if PRINT_FIELD | |||||
| stdioFile.printField(i + 1000000000UL, '\n'); | stdioFile.printField(i + 1000000000UL, '\n'); | ||||
| #else // PRINT_FIELD | |||||
| #else // PRINT_FIELD | |||||
| stdioFile.println(i + 1000000000UL); | stdioFile.println(i + 1000000000UL); | ||||
| #endif // PRINT_FIELD | |||||
| #endif // PRINT_FIELD | |||||
| } | } | ||||
| break; | break; | ||||
| case 4: | case 4: | ||||
| for (int j = 0; j < 100; j++) { | for (int j = 0; j < 100; j++) { | ||||
| for (uint8_t i = 0; i < 100; i++) { | for (uint8_t i = 0; i < 100; i++) { | ||||
| #if PRINT_FIELD | |||||
| #if PRINT_FIELD | |||||
| stdioFile.printField(f[i], '\n', 4); | stdioFile.printField(f[i], '\n', 4); | ||||
| #else // PRINT_FIELD | |||||
| stdioFile.println(f[i], 4); | |||||
| #endif // PRINT_FIELD | |||||
| #else // PRINT_FIELD | |||||
| stdioFile.println(f[i], 4); | |||||
| #endif // PRINT_FIELD | |||||
| } | } | ||||
| } | } | ||||
| break; | |||||
| break; | |||||
| default: | default: | ||||
| break; | break; | ||||
| } | } | ||||
| stdioFile.fflush(); | stdioFile.fflush(); | ||||
| stdioTime = millis() - stdioTime; | stdioTime = millis() - stdioTime; | ||||
| stdioSize = stdioFile.ftell(); | |||||
| stdioSize = stdioFile.ftell(); | |||||
| if (STDIO_LIST_COUNT) { | if (STDIO_LIST_COUNT) { | ||||
| size_t len; | size_t len; | ||||
| stdioFile.rewind(); | stdioFile.rewind(); | ||||
| for (int i = 0; i < STDIO_LIST_COUNT; i++) { | for (int i = 0; i < STDIO_LIST_COUNT; i++) { | ||||
| stdioFile.fgets(buf, sizeof(buf), &len); | stdioFile.fgets(buf, sizeof(buf), &len); | ||||
| Serial.print(len);Serial.print(','); | |||||
| Serial.print(len); | |||||
| Serial.print(','); | |||||
| Serial.print(buf); | Serial.print(buf); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| Serial.println(label[dataType]); | |||||
| Serial.println(label[dataType]); | |||||
| if (VERIFY_CONTENT && printSize == stdioSize) { | if (VERIFY_CONTENT && printSize == stdioSize) { | ||||
| printFile.rewind(); | printFile.rewind(); | ||||
| stdioFile.rewind(); | stdioFile.rewind(); | ||||
| Serial.print(printSize); | Serial.print(printSize); | ||||
| Serial.print(" != "); | Serial.print(" != "); | ||||
| } | } | ||||
| Serial.println(stdioSize); | |||||
| Serial.println(stdioSize); | |||||
| Serial.print("print millis: "); | Serial.print("print millis: "); | ||||
| Serial.println(printTime); | Serial.println(printTime); | ||||
| Serial.print("stdio millis: "); | Serial.print("stdio millis: "); | ||||
| Serial.print("ratio: "); | Serial.print("ratio: "); | ||||
| Serial.println((float)printTime/(float)stdioTime); | Serial.println((float)printTime/(float)stdioTime); | ||||
| Serial.println(); | Serial.println(); | ||||
| printFile.close(); | |||||
| stdioFile.fclose(); | |||||
| printFile.close(); | |||||
| stdioFile.fclose(); | |||||
| } | } | ||||
| Serial.println("Done"); | Serial.println("Done"); | ||||
| } | } |
| while(!Serial) {} | while(!Serial) {} | ||||
| Serial.println(F("Type any character to start")); | Serial.println(F("Type any character to start")); | ||||
| while (!Serial.available()) {} | while (!Serial.available()) {} | ||||
| // Initialize the SD. | // Initialize the SD. | ||||
| if (!SD.begin(csPin)) { | if (!SD.begin(csPin)) { | ||||
| Serial.println(F("begin error")); | Serial.println(F("begin error")); | ||||
| return; | return; | ||||
| } | } | ||||
| // Create and open the file. Use flag to truncate an existing file. | // Create and open the file. Use flag to truncate an existing file. | ||||
| file = SD.open("stream.txt", O_RDWR|O_CREAT|O_TRUNC); | |||||
| if (!file) { | |||||
| Serial.println(F("open error")); | |||||
| return; | |||||
| file = SD.open("stream.txt", O_RDWR|O_CREAT|O_TRUNC); | |||||
| if (!file) { | |||||
| Serial.println(F("open error")); | |||||
| return; | |||||
| } | } | ||||
| // Write a test number to the file. | // Write a test number to the file. | ||||
| file.println("12345"); | file.println("12345"); | ||||
| // Rewind the file and read the number with parseInt(). | // Rewind the file and read the number with parseInt(). | ||||
| file.seek(0); | file.seek(0); | ||||
| int i = file.parseInt(); | int i = file.parseInt(); | ||||
| Serial.print(F("parseInt: ")); | Serial.print(F("parseInt: ")); | ||||
| Serial.println(i); | |||||
| Serial.println(i); | |||||
| file.close(); | file.close(); | ||||
| } | } | ||||
| #include <SPI.h> | #include <SPI.h> | ||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| #include <SdFatUtil.h> | #include <SdFatUtil.h> | ||||
| #if USE_MULTIPLE_SPI_TYPES // Must be nonzero in SdFat/SdFatConfig.h | |||||
| #if SD_SPI_CONFIGURATION >= 3 // Must be set in SdFat/SdFatConfig.h | |||||
| // SD1 is a microSD on hardware SPI pins 50-52 | // SD1 is a microSD on hardware SPI pins 50-52 | ||||
| // Using my fast custom SPI | // Using my fast custom SPI | ||||
| SdFat sd1; | SdFat sd1; | ||||
| const uint8_t SD1_CS = 53; | const uint8_t SD1_CS = 53; | ||||
| // SD2 is a Catalex shield on hardware SPI pins 50-52 | // SD2 is a Catalex shield on hardware SPI pins 50-52 | ||||
| // Using the standard Arduino SPI library | // Using the standard Arduino SPI library | ||||
| SdFatLibSpi sd2; | SdFatLibSpi sd2; | ||||
| #define initError(msg) initErrorHalt(F(msg)) | #define initError(msg) initErrorHalt(F(msg)) | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void list() { | void list() { | ||||
| // list current directory on both cards | |||||
| // list current directory on all cards | |||||
| Serial.println(F("------sd1-------")); | Serial.println(F("------sd1-------")); | ||||
| sd1.ls("/", LS_SIZE|LS_R); | sd1.ls("/", LS_SIZE|LS_R); | ||||
| Serial.println(F("------sd2-------")); | Serial.println(F("------sd2-------")); | ||||
| sd2.ls("/", LS_SIZE|LS_R); | sd2.ls("/", LS_SIZE|LS_R); | ||||
| Serial.println(F("------sd3-------")); | Serial.println(F("------sd3-------")); | ||||
| sd3.ls("/", LS_SIZE|LS_R); | |||||
| Serial.println(F("---------------------")); | |||||
| sd3.ls("/", LS_SIZE|LS_R); | |||||
| Serial.println(F("---------------------")); | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void setup() { | void setup() { | ||||
| Serial.print(F("FreeRam: ")); | Serial.print(F("FreeRam: ")); | ||||
| Serial.println(FreeRam()); | Serial.println(FreeRam()); | ||||
| // fill buffer with known data | // fill buffer with known data | ||||
| for (int i = 0; i < sizeof(buf); i++) buf[i] = i; | |||||
| for (int i = 0; i < sizeof(buf); i++) { | |||||
| buf[i] = i; | |||||
| } | |||||
| Serial.println(F("type any character to start")); | Serial.println(F("type any character to start")); | ||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| // disable sd2 while initializing sd1 | // disable sd2 while initializing sd1 | ||||
| pinMode(SD2_CS, OUTPUT); | pinMode(SD2_CS, OUTPUT); | ||||
| digitalWrite(SD2_CS, HIGH); | digitalWrite(SD2_CS, HIGH); | ||||
| // initialize the first card | // initialize the first card | ||||
| if (!sd1.begin(SD1_CS)) sd1.initError("sd1:"); | |||||
| if (!sd1.begin(SD1_CS)) { | |||||
| sd1.initError("sd1:"); | |||||
| } | |||||
| // initialize the second card | // initialize the second card | ||||
| if (!sd2.begin(SD2_CS)) sd2.initError("sd2:"); | |||||
| if (!sd2.begin(SD2_CS)) { | |||||
| sd2.initError("sd2:"); | |||||
| } | |||||
| // initialize the third card | // initialize the third card | ||||
| if (!sd3.begin(SD3_CS)) sd3.initError("sd3:"); | |||||
| if (!sd3.begin(SD3_CS)) { | |||||
| sd3.initError("sd3:"); | |||||
| } | |||||
| Serial.println(F("Cards OK - creating directories")); | Serial.println(F("Cards OK - creating directories")); | ||||
| // create DIR1 on sd1 if it does not exist | |||||
| if (!sd1.exists("/DIR1")) { | |||||
| if (!sd1.mkdir("/DIR1")) sd1.errorExit("sd1.mkdir"); | |||||
| } | |||||
| // make /DIR1 the default directory for sd1 | |||||
| if (!sd1.chdir("/DIR1")) sd1.errorExit("sd1.chdir"); | |||||
| // create DIR2 on sd2 if it does not exist | |||||
| if (!sd2.exists("/DIR2")) { | |||||
| if (!sd2.mkdir("/DIR2")) sd2.errorExit("sd2.mkdir"); | |||||
| } | |||||
| // make /DIR2 the default directory for sd2 | |||||
| if (!sd2.chdir("/DIR2")) sd2.errorExit("sd2.chdir"); | |||||
| // create DIR3 on sd3 if it does not exist | |||||
| if (!sd3.exists("/DIR3")) { | |||||
| if (!sd3.mkdir("/DIR3")) sd2.errorExit("sd3.mkdir"); | |||||
| } | |||||
| // make /DIR3 the default directory for sd3 | |||||
| if (!sd3.chdir("/DIR3")) sd3.errorExit("sd3.chdir"); | |||||
| // create Dir1 on sd1 if it does not exist | |||||
| if (!sd1.exists("/Dir1")) { | |||||
| if (!sd1.mkdir("/Dir1")) { | |||||
| sd1.errorExit("sd1.mkdir"); | |||||
| } | |||||
| } | |||||
| // make /Dir1 the default directory for sd1 | |||||
| if (!sd1.chdir("/Dir1")) { | |||||
| sd1.errorExit("sd1.chdir"); | |||||
| } | |||||
| // create Dir2 on sd2 if it does not exist | |||||
| if (!sd2.exists("/Dir2")) { | |||||
| if (!sd2.mkdir("/Dir2")) { | |||||
| sd2.errorExit("sd2.mkdir"); | |||||
| } | |||||
| } | |||||
| // make /Dir2 the default directory for sd2 | |||||
| if (!sd2.chdir("/Dir2")) { | |||||
| sd2.errorExit("sd2.chdir"); | |||||
| } | |||||
| // create Dir3 on sd3 if it does not exist | |||||
| if (!sd3.exists("/Dir3")) { | |||||
| if (!sd3.mkdir("/Dir3")) { | |||||
| sd2.errorExit("sd3.mkdir"); | |||||
| } | |||||
| } | |||||
| // make /Dir3 the default directory for sd3 | |||||
| if (!sd3.chdir("/Dir3")) { | |||||
| sd3.errorExit("sd3.chdir"); | |||||
| } | |||||
| Serial.println(F("Directories created - removing old files")); | Serial.println(F("Directories created - removing old files")); | ||||
| if (sd1.exists("TEST1.BIN")) { | |||||
| if (!sd1.remove("TEST1.BIN")) sd1.errorExit("sd1.remove"); | |||||
| } | |||||
| if (sd2.exists("TEST2.BIN")) { | |||||
| if (!sd2.remove("TEST2.BIN")) sd2.errorExit("sd2.remove"); | |||||
| } | |||||
| if (sd3.exists("TEST3.BIN")) { | |||||
| if (!sd3.remove("TEST3.BIN")) sd2.errorExit("sd3.remove"); | |||||
| } | |||||
| if (sd1.exists("TEST1.bin")) { | |||||
| if (!sd1.remove("TEST1.bin")) { | |||||
| sd1.errorExit("sd1.remove"); | |||||
| } | |||||
| } | |||||
| if (sd2.exists("TEST2.bin")) { | |||||
| if (!sd2.remove("TEST2.bin")) { | |||||
| sd2.errorExit("sd2.remove"); | |||||
| } | |||||
| } | |||||
| if (sd3.exists("TEST3.bin")) { | |||||
| if (!sd3.remove("TEST3.bin")) { | |||||
| sd2.errorExit("sd3.remove"); | |||||
| } | |||||
| } | |||||
| Serial.println("Initial SD directories"); | Serial.println("Initial SD directories"); | ||||
| list(); | list(); | ||||
| // create or open /DIR1/TEST1.BIN and truncate it to zero length | |||||
| // create or open /Dir1/TEST1.bin and truncate it to zero length | |||||
| SdFile file1; | SdFile file1; | ||||
| if (!file1.open(&sd1, "TEST1.BIN", O_RDWR | O_CREAT | O_TRUNC)) { | |||||
| if (!file1.open(&sd1, "TEST1.bin", O_RDWR | O_CREAT | O_TRUNC)) { | |||||
| sd1.errorExit("file1"); | sd1.errorExit("file1"); | ||||
| } | } | ||||
| Serial.println(F("Writing SD1:/DIR1/TEST1.BIN")); | |||||
| // write data to /DIR1/TEST.BIN on sd1 | |||||
| Serial.println(F("Writing SD1:/Dir1/TEST1.bin")); | |||||
| // write data to /Dir1/TEST1.bin on sd1 | |||||
| for (int i = 0; i < NWRITE; i++) { | for (int i = 0; i < NWRITE; i++) { | ||||
| if (file1.write(buf, sizeof(buf)) != sizeof(buf)) { | if (file1.write(buf, sizeof(buf)) != sizeof(buf)) { | ||||
| sd1.errorExit("sd1.write"); | sd1.errorExit("sd1.write"); | ||||
| } | } | ||||
| file1.sync(); | file1.sync(); | ||||
| list(); | list(); | ||||
| // create or open /DIR2/TEST2.BIN and truncate it to zero length | |||||
| // create or open /Dir2/TEST2.bin and truncate it to zero length | |||||
| SdFile file2; | SdFile file2; | ||||
| if (!file2.open(&sd2, "TEST2.BIN", O_RDWR | O_CREAT | O_TRUNC)) { | |||||
| if (!file2.open(&sd2, "TEST2.bin", O_RDWR | O_CREAT | O_TRUNC)) { | |||||
| sd2.errorExit("file2"); | sd2.errorExit("file2"); | ||||
| } | } | ||||
| Serial.println(F("Copying SD1:/DIR1/TEST1.BIN to SD2::/DIR2/TEST2.BIN")); | |||||
| Serial.println(F("Copying SD1:/Dir1/TEST1.bin to SD2::/Dir2/TEST2.bin")); | |||||
| // copy file1 to file2 | // copy file1 to file2 | ||||
| file1.rewind(); | file1.rewind(); | ||||
| uint32_t t = millis(); | uint32_t t = millis(); | ||||
| while (1) { | while (1) { | ||||
| int n = file1.read(buf, sizeof(buf)); | int n = file1.read(buf, sizeof(buf)); | ||||
| if (n < 0) sd1.errorExit("read1"); | |||||
| if (n == 0) break; | |||||
| if (file2.write(buf, n) != n) sd2.errorExit("write3"); | |||||
| if (n < 0) { | |||||
| sd1.errorExit("read1"); | |||||
| } | |||||
| if (n == 0) { | |||||
| break; | |||||
| } | |||||
| if (file2.write(buf, n) != n) { | |||||
| sd2.errorExit("write3"); | |||||
| } | |||||
| } | } | ||||
| t = millis() - t; | t = millis() - t; | ||||
| file2.sync(); | file2.sync(); | ||||
| Serial.print(t); | Serial.print(t); | ||||
| Serial.println(F(" millis")); | Serial.println(F(" millis")); | ||||
| list(); | list(); | ||||
| // create or open /DIR3/TEST3.BIN and truncate it to zero length | |||||
| // create or open /Dir3/TEST3.bin and truncate it to zero length | |||||
| SdFile file3; | SdFile file3; | ||||
| if (!file3.open(&sd3, "TEST3.BIN", O_RDWR | O_CREAT | O_TRUNC)) { | |||||
| if (!file3.open(&sd3, "TEST3.bin", O_RDWR | O_CREAT | O_TRUNC)) { | |||||
| sd3.errorExit("file3"); | sd3.errorExit("file3"); | ||||
| } | } | ||||
| file2.rewind(); | file2.rewind(); | ||||
| Serial.println(F("Copying SD2:/DIR2/TEST2.BIN to SD3:/DIR3/TEST3.BIN")); | |||||
| Serial.println(F("Copying SD2:/Dir2/TEST2.bin to SD3:/Dir3/TEST3.bin")); | |||||
| while (1) { | while (1) { | ||||
| int n = file2.read(buf, sizeof(buf)); | int n = file2.read(buf, sizeof(buf)); | ||||
| if (n == 0) break; | |||||
| if (n != sizeof(buf)) sd2.errorExit("read2"); | |||||
| if (file3.write(buf, n) != n) sd3.errorExit("write2"); | |||||
| if (n == 0) { | |||||
| break; | |||||
| } | |||||
| if (n != sizeof(buf)) { | |||||
| sd2.errorExit("read2"); | |||||
| } | |||||
| if (file3.write(buf, n) != n) { | |||||
| sd3.errorExit("write2"); | |||||
| } | |||||
| } | } | ||||
| file3.sync(); | file3.sync(); | ||||
| list(); | list(); | ||||
| // Verify content of file3 | // Verify content of file3 | ||||
| file3.rewind(); | file3.rewind(); | ||||
| Serial.println(F("Verifying content of TEST3.BIN")); | |||||
| Serial.println(F("Verifying content of TEST3.bin")); | |||||
| for (int i = 0; i < NWRITE; i++) { | for (int i = 0; i < NWRITE; i++) { | ||||
| if (file3.read(buf, sizeof(buf)) != sizeof(buf)) { | if (file3.read(buf, sizeof(buf)) != sizeof(buf)) { | ||||
| sd3.errorExit("sd3.read"); | sd3.errorExit("sd3.read"); | ||||
| } | } | ||||
| for (int j = 0; j < sizeof(buf); j++) { | for (int j = 0; j < sizeof(buf); j++) { | ||||
| if (j != buf[j]) sd3.errorExit("Verify error"); | |||||
| if (j != buf[j]) { | |||||
| sd3.errorExit("Verify error"); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| Serial.println(F("Done - Verify OK")); | Serial.println(F("Done - Verify OK")); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void loop() {} | void loop() {} | ||||
| #else // USE_MULTIPLE_SPI_TYPES | |||||
| #error USE_MULTIPLE_SPI_TYPES must be set nonzero in SdFat/SdFatConfig.h | |||||
| #endif //USE_MULTIPLE_SPI_TYPES | |||||
| #else // SD_SPI_CONFIGURATION >= 3 | |||||
| #error SD_SPI_CONFIGURATION must be set to 3 in SdFat/SdFatConfig.h | |||||
| #endif //SD_SPI_CONFIGURATION >= 3 |
| /* | /* | ||||
| * This sketch tests the dateTimeCallback() function | |||||
| * This program tests the dateTimeCallback() function | |||||
| * and the timestamp() function. | * and the timestamp() function. | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| SdFat sd; | SdFat sd; | ||||
| */ | */ | ||||
| void printTimestamps(SdFile& f) { | void printTimestamps(SdFile& f) { | ||||
| dir_t d; | dir_t d; | ||||
| if (!f.dirEntry(&d)) error("f.dirEntry failed"); | |||||
| if (!f.dirEntry(&d)) { | |||||
| error("f.dirEntry failed"); | |||||
| } | |||||
| cout << pstr("Creation: "); | |||||
| cout << F("Creation: "); | |||||
| f.printFatDate(d.creationDate); | f.printFatDate(d.creationDate); | ||||
| cout << ' '; | cout << ' '; | ||||
| f.printFatTime(d.creationTime); | f.printFatTime(d.creationTime); | ||||
| cout << endl; | cout << endl; | ||||
| cout << pstr("Modify: "); | |||||
| cout << F("Modify: "); | |||||
| f.printFatDate(d.lastWriteDate); | f.printFatDate(d.lastWriteDate); | ||||
| cout <<' '; | cout <<' '; | ||||
| f.printFatTime(d.lastWriteTime); | f.printFatTime(d.lastWriteTime); | ||||
| cout << endl; | cout << endl; | ||||
| cout << pstr("Access: "); | |||||
| cout << F("Access: "); | |||||
| f.printFatDate(d.lastAccessDate); | f.printFatDate(d.lastAccessDate); | ||||
| cout << endl; | cout << endl; | ||||
| } | } | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| cout << pstr("Type any character to start\n"); | |||||
| cout << F("Type any character to start\n"); | |||||
| while (!Serial.available()); | while (!Serial.available()); | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // remove files if they exist | // remove files if they exist | ||||
| sd.remove("CALLBACK.TXT"); | |||||
| sd.remove("DEFAULT.TXT"); | |||||
| sd.remove("STAMP.TXT"); | |||||
| sd.remove("callback.txt"); | |||||
| sd.remove("default.txt"); | |||||
| sd.remove("stamp.txt"); | |||||
| // create a new file with default timestamps | // create a new file with default timestamps | ||||
| if (!file.open("DEFAULT.TXT", O_CREAT | O_WRITE)) { | |||||
| error("open DEFAULT.TXT failed"); | |||||
| if (!file.open("default.txt", O_CREAT | O_WRITE)) { | |||||
| error("open default.txt failed"); | |||||
| } | } | ||||
| cout << pstr("\nOpen with default times\n"); | |||||
| cout << F("\nOpen with default times\n"); | |||||
| printTimestamps(file); | printTimestamps(file); | ||||
| // close file | // close file | ||||
| SdFile::dateTimeCallback(dateTime); | SdFile::dateTimeCallback(dateTime); | ||||
| // create a new file with callback timestamps | // create a new file with callback timestamps | ||||
| if (!file.open("CALLBACK.TXT", O_CREAT | O_WRITE)) { | |||||
| error("open CALLBACK.TXT failed"); | |||||
| if (!file.open("callback.txt", O_CREAT | O_WRITE)) { | |||||
| error("open callback.txt failed"); | |||||
| } | } | ||||
| cout << ("\nOpen with callback times\n"); | cout << ("\nOpen with callback times\n"); | ||||
| printTimestamps(file); | printTimestamps(file); | ||||
| // force dir update | // force dir update | ||||
| file.sync(); | file.sync(); | ||||
| cout << pstr("\nTimes after write\n"); | |||||
| cout << F("\nTimes after write\n"); | |||||
| printTimestamps(file); | printTimestamps(file); | ||||
| // close file | // close file | ||||
| SdFile::dateTimeCallbackCancel(); | SdFile::dateTimeCallbackCancel(); | ||||
| // create a new file with default timestamps | // create a new file with default timestamps | ||||
| if (!file.open("STAMP.TXT", O_CREAT | O_WRITE)) { | |||||
| error("open STAMP.TXT failed"); | |||||
| if (!file.open("stamp.txt", O_CREAT | O_WRITE)) { | |||||
| error("open stamp.txt failed"); | |||||
| } | } | ||||
| // set creation date time | // set creation date time | ||||
| if (!file.timestamp(T_CREATE, 2014, 11, 10, 1, 2, 3)) { | if (!file.timestamp(T_CREATE, 2014, 11, 10, 1, 2, 3)) { | ||||
| if (!file.timestamp(T_ACCESS, 2014, 11, 12, 7, 8, 9)) { | if (!file.timestamp(T_ACCESS, 2014, 11, 12, 7, 8, 9)) { | ||||
| error("set access time failed"); | error("set access time failed"); | ||||
| } | } | ||||
| cout << pstr("\nTimes after timestamp() calls\n"); | |||||
| cout << F("\nTimes after timestamp() calls\n"); | |||||
| printTimestamps(file); | printTimestamps(file); | ||||
| file.close(); | file.close(); | ||||
| cout << pstr("\nDone\n"); | |||||
| cout << F("\nDone\n"); | |||||
| } | } | ||||
| void loop(void){} | |||||
| void loop(void) {} |
| const uint8_t SD1_CS = 10; // chip select for sd1 | const uint8_t SD1_CS = 10; // chip select for sd1 | ||||
| SdFat sd2; | SdFat sd2; | ||||
| const uint8_t SD2_CS = 9; // chip select for sd2 | |||||
| const uint8_t SD2_CS = 4; // chip select for sd2 | |||||
| const uint8_t BUF_DIM = 100; | const uint8_t BUF_DIM = 100; | ||||
| uint8_t buf[BUF_DIM]; | uint8_t buf[BUF_DIM]; | ||||
| Serial.print(F("FreeRam: ")); | Serial.print(F("FreeRam: ")); | ||||
| Serial.println(FreeRam()); | Serial.println(FreeRam()); | ||||
| // fill buffer with known data | // fill buffer with known data | ||||
| for (int i = 0; i < sizeof(buf); i++) buf[i] = i; | |||||
| for (int i = 0; i < sizeof(buf); i++) { | |||||
| buf[i] = i; | |||||
| } | |||||
| Serial.println(F("type any character to start")); | Serial.println(F("type any character to start")); | ||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| // disable sd2 while initializing sd1 | // disable sd2 while initializing sd1 | ||||
| pinMode(SD2_CS, OUTPUT); | pinMode(SD2_CS, OUTPUT); | ||||
| digitalWrite(SD2_CS, HIGH); | digitalWrite(SD2_CS, HIGH); | ||||
| // initialize the first card | // initialize the first card | ||||
| if (!sd1.begin(SD1_CS)) { | if (!sd1.begin(SD1_CS)) { | ||||
| sd1.initError("sd1:"); | sd1.initError("sd1:"); | ||||
| } | } | ||||
| // create DIR1 on sd1 if it does not exist | |||||
| if (!sd1.exists("/DIR1")) { | |||||
| if (!sd1.mkdir("/DIR1")) sd1.errorExit("sd1.mkdir"); | |||||
| // create Dir1 on sd1 if it does not exist | |||||
| if (!sd1.exists("/Dir1")) { | |||||
| if (!sd1.mkdir("/Dir1")) { | |||||
| sd1.errorExit("sd1.mkdir"); | |||||
| } | |||||
| } | } | ||||
| // initialize the second card | // initialize the second card | ||||
| if (!sd2.begin(SD2_CS)) { | if (!sd2.begin(SD2_CS)) { | ||||
| sd2.initError("sd2:"); | sd2.initError("sd2:"); | ||||
| } | } | ||||
| // create DIR2 on sd2 if it does not exist | |||||
| if (!sd2.exists("/DIR2")) { | |||||
| if (!sd2.mkdir("/DIR2")) sd2.errorExit("sd2.mkdir"); | |||||
| // create Dir2 on sd2 if it does not exist | |||||
| if (!sd2.exists("/Dir2")) { | |||||
| if (!sd2.mkdir("/Dir2")) { | |||||
| sd2.errorExit("sd2.mkdir"); | |||||
| } | |||||
| } | } | ||||
| // list root directory on both cards | // list root directory on both cards | ||||
| Serial.println(F("------sd1 root-------")); | Serial.println(F("------sd1 root-------")); | ||||
| Serial.println(F("------sd2 root-------")); | Serial.println(F("------sd2 root-------")); | ||||
| sd2.ls(); | sd2.ls(); | ||||
| // make /DIR1 the default directory for sd1 | |||||
| if (!sd1.chdir("/DIR1")) sd1.errorExit("sd1.chdir"); | |||||
| // make /DIR2 the default directory for sd2 | |||||
| if (!sd2.chdir("/DIR2")) sd2.errorExit("sd2.chdir"); | |||||
| // make /Dir1 the default directory for sd1 | |||||
| if (!sd1.chdir("/Dir1")) { | |||||
| sd1.errorExit("sd1.chdir"); | |||||
| } | |||||
| // make /Dir2 the default directory for sd2 | |||||
| if (!sd2.chdir("/Dir2")) { | |||||
| sd2.errorExit("sd2.chdir"); | |||||
| } | |||||
| // list current directory on both cards | // list current directory on both cards | ||||
| Serial.println(F("------sd1 DIR1-------")); | |||||
| Serial.println(F("------sd1 Dir1-------")); | |||||
| sd1.ls(); | sd1.ls(); | ||||
| Serial.println(F("------sd2 DIR2-------")); | |||||
| Serial.println(F("------sd2 Dir2-------")); | |||||
| sd2.ls(); | sd2.ls(); | ||||
| Serial.println(F("---------------------")); | Serial.println(F("---------------------")); | ||||
| // remove RENAME.BIN from /DIR2 directory of sd2 | |||||
| if (sd2.exists("RENAME.BIN")) { | |||||
| if (!sd2.remove("RENAME.BIN")) { | |||||
| sd2.errorExit("remove RENAME.BIN"); | |||||
| // remove rename.bin from /Dir2 directory of sd2 | |||||
| if (sd2.exists("rename.bin")) { | |||||
| if (!sd2.remove("rename.bin")) { | |||||
| sd2.errorExit("remove rename.bin"); | |||||
| } | } | ||||
| } | } | ||||
| // set the current working directory for open() to sd1 | // set the current working directory for open() to sd1 | ||||
| sd1.chvol(); | sd1.chvol(); | ||||
| // create or open /DIR1/TEST.BIN and truncate it to zero length | |||||
| // create or open /Dir1/test.bin and truncate it to zero length | |||||
| SdFile file1; | SdFile file1; | ||||
| if (!file1.open("TEST.BIN", O_RDWR | O_CREAT | O_TRUNC)) { | |||||
| if (!file1.open("test.bin", O_RDWR | O_CREAT | O_TRUNC)) { | |||||
| sd1.errorExit("file1"); | sd1.errorExit("file1"); | ||||
| } | } | ||||
| Serial.println(F("Writing TEST.BIN to sd1")); | |||||
| // write data to /DIR1/TEST.BIN on sd1 | |||||
| Serial.println(F("Writing test.bin to sd1")); | |||||
| // write data to /Dir1/test.bin on sd1 | |||||
| for (int i = 0; i < NWRITE; i++) { | for (int i = 0; i < NWRITE; i++) { | ||||
| if (file1.write(buf, sizeof(buf)) != sizeof(buf)) { | if (file1.write(buf, sizeof(buf)) != sizeof(buf)) { | ||||
| sd1.errorExit("sd1.write"); | sd1.errorExit("sd1.write"); | ||||
| } | } | ||||
| // set the current working directory for open() to sd2 | // set the current working directory for open() to sd2 | ||||
| sd2.chvol(); | sd2.chvol(); | ||||
| // create or open /DIR2/COPY.BIN and truncate it to zero length | |||||
| // create or open /Dir2/copy.bin and truncate it to zero length | |||||
| SdFile file2; | SdFile file2; | ||||
| if (!file2.open("COPY.BIN", O_WRITE | O_CREAT | O_TRUNC)) { | |||||
| if (!file2.open("copy.bin", O_WRITE | O_CREAT | O_TRUNC)) { | |||||
| sd2.errorExit("file2"); | sd2.errorExit("file2"); | ||||
| } | } | ||||
| Serial.println(F("Copying TEST.BIN to COPY.BIN")); | |||||
| Serial.println(F("Copying test.bin to copy.bin")); | |||||
| // copy file1 to file2 | // copy file1 to file2 | ||||
| file1.rewind(); | file1.rewind(); | ||||
| uint32_t t = millis(); | uint32_t t = millis(); | ||||
| while (1) { | while (1) { | ||||
| int n = file1.read(buf, sizeof(buf)); | int n = file1.read(buf, sizeof(buf)); | ||||
| if (n < 0) sd1.errorExit("read1"); | |||||
| if (n == 0) break; | |||||
| if (file2.write(buf, n) != n) sd2.errorExit("write2"); | |||||
| if (n < 0) { | |||||
| sd1.errorExit("read1"); | |||||
| } | |||||
| if (n == 0) { | |||||
| break; | |||||
| } | |||||
| if (file2.write(buf, n) != n) { | |||||
| sd2.errorExit("write2"); | |||||
| } | |||||
| } | } | ||||
| t = millis() - t; | t = millis() - t; | ||||
| Serial.print(F("File size: ")); | Serial.print(F("File size: ")); | ||||
| Serial.print(F("Copy time: ")); | Serial.print(F("Copy time: ")); | ||||
| Serial.print(t); | Serial.print(t); | ||||
| Serial.println(F(" millis")); | Serial.println(F(" millis")); | ||||
| // close TEST.BIN | |||||
| // close test.bin | |||||
| file1.close(); | file1.close(); | ||||
| file2.close(); | |||||
| // list current directory on both cards | |||||
| Serial.println(F("------sd1 -------")); | |||||
| sd1.ls("/", LS_R | LS_DATE | LS_SIZE); | |||||
| Serial.println(F("------sd2 -------")); | |||||
| sd2.ls("/", LS_R | LS_DATE | LS_SIZE); | |||||
| Serial.println(F("---------------------")); | |||||
| Serial.println(F("Renaming copy.bin")); | |||||
| // rename the copy | // rename the copy | ||||
| file2.close(); | |||||
| if (!sd2.rename("COPY.BIN", "RENAME.BIN")) { | |||||
| if (!sd2.rename("copy.bin", "rename.bin")) { | |||||
| sd2.errorExit("sd2.rename"); | sd2.errorExit("sd2.rename"); | ||||
| } | } | ||||
| // list current directory on both cards | |||||
| Serial.println(F("------sd1 -------")); | |||||
| sd1.ls("/", LS_R | LS_DATE | LS_SIZE); | |||||
| Serial.println(F("------sd2 -------")); | |||||
| sd2.ls("/", LS_R | LS_DATE | LS_SIZE); | |||||
| Serial.println(F("---------------------")); | |||||
| Serial.println(F("Done")); | Serial.println(F("Done")); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ |
| /* | /* | ||||
| * This sketch is a simple binary write/read benchmark. | |||||
| * This program is a simple binary write/read benchmark. | |||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| #include <SdFatUtil.h> | #include <SdFatUtil.h> | ||||
| if (!sd.card()->readCID(&cid)) { | if (!sd.card()->readCID(&cid)) { | ||||
| error("readCID failed"); | error("readCID failed"); | ||||
| } | } | ||||
| cout << pstr("\nManufacturer ID: "); | |||||
| cout << F("\nManufacturer ID: "); | |||||
| cout << hex << int(cid.mid) << dec << endl; | cout << hex << int(cid.mid) << dec << endl; | ||||
| cout << pstr("OEM ID: ") << cid.oid[0] << cid.oid[1] << endl; | |||||
| cout << pstr("Product: "); | |||||
| cout << F("OEM ID: ") << cid.oid[0] << cid.oid[1] << endl; | |||||
| cout << F("Product: "); | |||||
| for (uint8_t i = 0; i < 5; i++) { | for (uint8_t i = 0; i < 5; i++) { | ||||
| cout << cid.pnm[i]; | cout << cid.pnm[i]; | ||||
| } | } | ||||
| cout << pstr("\nVersion: "); | |||||
| cout << F("\nVersion: "); | |||||
| cout << int(cid.prv_n) << '.' << int(cid.prv_m) << endl; | cout << int(cid.prv_n) << '.' << int(cid.prv_m) << endl; | ||||
| cout << pstr("Serial number: ") << hex << cid.psn << dec << endl; | |||||
| cout << pstr("Manufacturing date: "); | |||||
| cout << F("Serial number: ") << hex << cid.psn << dec << endl; | |||||
| cout << F("Manufacturing date: "); | |||||
| cout << int(cid.mdt_month) << '/'; | cout << int(cid.mdt_month) << '/'; | ||||
| cout << (2000 + cid.mdt_year_low + 10 * cid.mdt_year_high) << endl; | cout << (2000 + cid.mdt_year_low + 10 * cid.mdt_year_high) << endl; | ||||
| cout << endl; | cout << endl; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void setup() { | void setup() { | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial){} // wait for Leonardo | |||||
| cout << pstr("\nUse a freshly formatted SD for best performance.\n"); | |||||
| while (!Serial) {} // wait for Leonardo | |||||
| delay(1000); | |||||
| cout << F("\nUse a freshly formatted SD for best performance.\n"); | |||||
| // use uppercase in hex and use 0X base prefix | // use uppercase in hex and use 0X base prefix | ||||
| cout << uppercase << showbase << endl; | cout << uppercase << showbase << endl; | ||||
| } | } | ||||
| // discard any input | // discard any input | ||||
| while (Serial.read() >= 0) {} | while (Serial.read() >= 0) {} | ||||
| // pstr stores strings in flash to save RAM | |||||
| cout << pstr("Type any character to start\n"); | |||||
| // F( stores strings in flash to save RAM | |||||
| cout << F("Type any character to start\n"); | |||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| cout << pstr("Free RAM: ") << FreeRam() << endl; | |||||
| cout << F("Free RAM: ") << FreeRam() << endl; | |||||
| // initialize the SD card at SPI_FULL_SPEED for best performance. | // initialize the SD card at SPI_FULL_SPEED for best performance. | ||||
| // try SPI_HALF_SPEED if bus errors occur. | // try SPI_HALF_SPEED if bus errors occur. | ||||
| if (!sd.begin(chipSelect, SPI_FULL_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_FULL_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| cout << F("Type is FAT") << int(sd.vol()->fatType()) << endl; | |||||
| cout << F("Card size: ") << sd.card()->cardSize()*512E-9; | |||||
| cout << F(" GB (GB = 1E9 bytes)") << endl; | |||||
| cout << pstr("Type is FAT") << int(sd.vol()->fatType()) << endl; | |||||
| cout << pstr("Card size: ") << sd.card()->cardSize()*512E-9; | |||||
| cout << pstr(" GB (GB = 1E9 bytes)") << endl; | |||||
| cidDmp(); | cidDmp(); | ||||
| // open or create file - truncate existing file. | // open or create file - truncate existing file. | ||||
| if (!file.open("BENCH.DAT", O_CREAT | O_TRUNC | O_RDWR)) { | |||||
| if (!file.open("bench.dat", O_CREAT | O_TRUNC | O_RDWR)) { | |||||
| error("open failed"); | error("open failed"); | ||||
| } | } | ||||
| buf[BUF_SIZE-2] = '\r'; | buf[BUF_SIZE-2] = '\r'; | ||||
| buf[BUF_SIZE-1] = '\n'; | buf[BUF_SIZE-1] = '\n'; | ||||
| cout << pstr("File size ") << FILE_SIZE_MB << pstr(" MB\n"); | |||||
| cout << pstr("Buffer size ") << BUF_SIZE << pstr(" bytes\n"); | |||||
| cout << pstr("Starting write test, please wait.") << endl << endl; | |||||
| cout << F("File size ") << FILE_SIZE_MB << F(" MB\n"); | |||||
| cout << F("Buffer size ") << BUF_SIZE << F(" bytes\n"); | |||||
| cout << F("Starting write test, please wait.") << endl << endl; | |||||
| // do write test | // do write test | ||||
| uint32_t n = FILE_SIZE/sizeof(buf); | uint32_t n = FILE_SIZE/sizeof(buf); | ||||
| cout <<pstr("write speed and latency") << endl; | |||||
| cout << pstr("speed,max,min,avg") << endl; | |||||
| cout << pstr("KB/Sec,usec,usec,usec") << endl; | |||||
| cout <<F("write speed and latency") << endl; | |||||
| cout << F("speed,max,min,avg") << endl; | |||||
| cout << F("KB/Sec,usec,usec,usec") << endl; | |||||
| for (uint8_t nTest = 0; nTest < WRITE_COUNT; nTest++) { | for (uint8_t nTest = 0; nTest < WRITE_COUNT; nTest++) { | ||||
| file.truncate(0); | file.truncate(0); | ||||
| maxLatency = 0; | maxLatency = 0; | ||||
| error("write failed"); | error("write failed"); | ||||
| } | } | ||||
| m = micros() - m; | m = micros() - m; | ||||
| if (maxLatency < m) maxLatency = m; | |||||
| if (minLatency > m) minLatency = m; | |||||
| if (maxLatency < m) { | |||||
| maxLatency = m; | |||||
| } | |||||
| if (minLatency > m) { | |||||
| minLatency = m; | |||||
| } | |||||
| totalLatency += m; | totalLatency += m; | ||||
| } | } | ||||
| file.sync(); | file.sync(); | ||||
| t = millis() - t; | t = millis() - t; | ||||
| s = file.fileSize(); | s = file.fileSize(); | ||||
| cout << s/t <<',' << maxLatency << ',' << minLatency; | |||||
| cout << s/t <<',' << maxLatency << ',' << minLatency; | |||||
| cout << ',' << totalLatency/n << endl; | cout << ',' << totalLatency/n << endl; | ||||
| } | } | ||||
| cout << endl << pstr("Starting read test, please wait.") << endl; | |||||
| cout << endl <<pstr("read speed and latency") << endl; | |||||
| cout << pstr("speed,max,min,avg") << endl; | |||||
| cout << pstr("KB/Sec,usec,usec,usec") << endl; | |||||
| cout << endl << F("Starting read test, please wait.") << endl; | |||||
| cout << endl <<F("read speed and latency") << endl; | |||||
| cout << F("speed,max,min,avg") << endl; | |||||
| cout << F("KB/Sec,usec,usec,usec") << endl; | |||||
| // do read test | // do read test | ||||
| for (uint8_t nTest = 0; nTest < READ_COUNT; nTest++) { | for (uint8_t nTest = 0; nTest < READ_COUNT; nTest++) { | ||||
| file.rewind(); | file.rewind(); | ||||
| error("read failed"); | error("read failed"); | ||||
| } | } | ||||
| m = micros() - m; | m = micros() - m; | ||||
| if (maxLatency < m) maxLatency = m; | |||||
| if (minLatency > m) minLatency = m; | |||||
| if (maxLatency < m) { | |||||
| maxLatency = m; | |||||
| } | |||||
| if (minLatency > m) { | |||||
| minLatency = m; | |||||
| } | |||||
| totalLatency += m; | totalLatency += m; | ||||
| if (buf[BUF_SIZE-1] != '\n') { | if (buf[BUF_SIZE-1] != '\n') { | ||||
| error("data check"); | error("data check"); | ||||
| } | } | ||||
| } | } | ||||
| t = millis() - t; | t = millis() - t; | ||||
| cout << s/t <<',' << maxLatency << ',' << minLatency; | |||||
| cout << s/t <<',' << maxLatency << ',' << minLatency; | |||||
| cout << ',' << totalLatency/n << endl; | cout << ',' << totalLatency/n << endl; | ||||
| } | } | ||||
| cout << endl << pstr("Done") << endl; | |||||
| cout << endl << F("Done") << endl; | |||||
| file.close(); | file.close(); | ||||
| } | } |
| /* | /* | ||||
| * Demo of ArduinoInStream and ArduinoOutStream | * Demo of ArduinoInStream and ArduinoOutStream | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // create serial output stream | // create serial output stream |
| /* | /* | ||||
| * Simple data logger. | * Simple data logger. | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // SD chip select pin. Be sure to disable any other SPI devices such as Enet. | // SD chip select pin. Be sure to disable any other SPI devices such as Enet. | ||||
| const uint8_t chipSelect = SS; | const uint8_t chipSelect = SS; | ||||
| // Interval between data records in milliseconds. | |||||
| // Interval between data records in milliseconds. | |||||
| // The interval must be greater than the maximum SD write latency plus the | // The interval must be greater than the maximum SD write latency plus the | ||||
| // time to acquire and write data to the SD to avoid overrun errors. | // time to acquire and write data to the SD to avoid overrun errors. | ||||
| // Run the bench example to check the quality of your SD card. | // Run the bench example to check the quality of your SD card. | ||||
| const uint32_t SAMPLE_INTERVAL_MS = 200; | const uint32_t SAMPLE_INTERVAL_MS = 200; | ||||
| // Log file base name. Must be six characters or less. | // Log file base name. Must be six characters or less. | ||||
| #define FILE_BASE_NAME "DATA" | |||||
| #define FILE_BASE_NAME "Data" | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // File system object. | // File system object. | ||||
| SdFat sd; | SdFat sd; | ||||
| // Log a data record. | // Log a data record. | ||||
| void logData() { | void logData() { | ||||
| uint16_t data[ANALOG_COUNT]; | uint16_t data[ANALOG_COUNT]; | ||||
| // Read all channels to avoid SD write latency between readings. | // Read all channels to avoid SD write latency between readings. | ||||
| for (uint8_t i = 0; i < ANALOG_COUNT; i++) { | for (uint8_t i = 0; i < ANALOG_COUNT; i++) { | ||||
| data[i] = analogRead(i); | data[i] = analogRead(i); | ||||
| } | } | ||||
| // Write data to file. Start with log time in micros. | // Write data to file. Start with log time in micros. | ||||
| file.print(logTime); | file.print(logTime); | ||||
| // Write ADC data to CSV record. | // Write ADC data to CSV record. | ||||
| for (uint8_t i = 0; i < ANALOG_COUNT; i++) { | for (uint8_t i = 0; i < ANALOG_COUNT; i++) { | ||||
| file.write(','); | file.write(','); | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void setup() { | void setup() { | ||||
| const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1; | const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1; | ||||
| char fileName[13] = FILE_BASE_NAME "00.CSV"; | |||||
| char fileName[13] = FILE_BASE_NAME "00.csv"; | |||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| delay(1000); | delay(1000); | ||||
| Serial.println(F("Type any character to start")); | Serial.println(F("Type any character to start")); | ||||
| while (!Serial.available()) {} | while (!Serial.available()) {} | ||||
| // Initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // Initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // Find an unused file name. | // Find an unused file name. | ||||
| if (BASE_NAME_SIZE > 6) { | if (BASE_NAME_SIZE > 6) { | ||||
| error("FILE_BASE_NAME too long"); | error("FILE_BASE_NAME too long"); | ||||
| error("Can't create file name"); | error("Can't create file name"); | ||||
| } | } | ||||
| } | } | ||||
| if (!file.open(fileName, O_CREAT | O_WRITE | O_EXCL)) error("file.open"); | |||||
| if (!file.open(fileName, O_CREAT | O_WRITE | O_EXCL)) { | |||||
| error("file.open"); | |||||
| } | |||||
| do { | do { | ||||
| delay(10); | delay(10); | ||||
| } while (Serial.read() >= 0); | } while (Serial.read() >= 0); | ||||
| Serial.print(F("Logging to: ")); | Serial.print(F("Logging to: ")); | ||||
| Serial.println(fileName); | Serial.println(fileName); | ||||
| Serial.println(F("Type any character to stop")); | Serial.println(F("Type any character to stop")); | ||||
| // Write data header. | // Write data header. | ||||
| writeHeader(); | writeHeader(); | ||||
| // Start on a multiple of the sample interval. | // Start on a multiple of the sample interval. | ||||
| logTime = micros()/(1000UL*SAMPLE_INTERVAL_MS) + 1; | logTime = micros()/(1000UL*SAMPLE_INTERVAL_MS) + 1; | ||||
| logTime *= 1000UL*SAMPLE_INTERVAL_MS; | logTime *= 1000UL*SAMPLE_INTERVAL_MS; | ||||
| void loop() { | void loop() { | ||||
| // Time for next record. | // Time for next record. | ||||
| logTime += 1000UL*SAMPLE_INTERVAL_MS; | logTime += 1000UL*SAMPLE_INTERVAL_MS; | ||||
| // Wait for log time. | // Wait for log time. | ||||
| int32_t diff; | int32_t diff; | ||||
| do { | do { | ||||
| diff = micros() - logTime; | diff = micros() - logTime; | ||||
| } while (diff < 0); | } while (diff < 0); | ||||
| // Check for data rate too high. | |||||
| if (diff > 10) error("Missed data record"); | |||||
| // Check for data rate too high. | |||||
| if (diff > 10) { | |||||
| error("Missed data record"); | |||||
| } | |||||
| logData(); | logData(); | ||||
| // Force data to SD and update the directory entry to avoid data loss. | // Force data to SD and update the directory entry to avoid data loss. | ||||
| if (!file.sync() || file.getWriteError()) error("write error"); | |||||
| if (!file.sync() || file.getWriteError()) { | |||||
| error("write error"); | |||||
| } | |||||
| if (Serial.available()) { | if (Serial.available()) { | ||||
| // Close file and stop. | // Close file and stop. | ||||
| file.close(); | file.close(); |
| /* | /* | ||||
| * Example use of chdir(), ls(), mkdir(), and rmdir(). | * Example use of chdir(), ls(), mkdir(), and rmdir(). | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // SD card chip select pin. | // SD card chip select pin. | ||||
| const uint8_t SD_CHIP_SELECT = SS; | const uint8_t SD_CHIP_SELECT = SS; | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| delay(1000); | delay(1000); | ||||
| cout << pstr("Type any character to start\n"); | |||||
| cout << F("Type any character to start\n"); | |||||
| // Wait for input line and discard. | // Wait for input line and discard. | ||||
| cin.readline(); | cin.readline(); | ||||
| // Initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // Initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| if (!sd.begin(SD_CHIP_SELECT, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(SD_CHIP_SELECT, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // Check for empty SD. | // Check for empty SD. | ||||
| if (file.openNext(sd.vwd(), O_READ)) { | if (file.openNext(sd.vwd(), O_READ)) { | ||||
| cout << pstr("Found files/folders in the root directory.\n"); | |||||
| cout << F("Found files/folders in the root directory.\n"); | |||||
| if (!ALLOW_WIPE) { | if (!ALLOW_WIPE) { | ||||
| error("SD not empty, use a blank SD or set ALLOW_WIPE true."); | |||||
| error("SD not empty, use a blank SD or set ALLOW_WIPE true."); | |||||
| } else { | } else { | ||||
| cout << pstr("Type: 'WIPE' to delete all SD files.\n"); | |||||
| cout << F("Type: 'WIPE' to delete all SD files.\n"); | |||||
| char buf[10]; | char buf[10]; | ||||
| cin.readline(); | cin.readline(); | ||||
| cin.get(buf, sizeof(buf)); | cin.get(buf, sizeof(buf)); | ||||
| error("Invalid WIPE input"); | error("Invalid WIPE input"); | ||||
| } | } | ||||
| file.close(); | file.close(); | ||||
| sd.vwd()->rmRfStar(); | |||||
| cout << pstr("***SD wiped clean.***\n\n"); | |||||
| if (!sd.vwd()->rmRfStar()) { | |||||
| error("wipe failed"); | |||||
| } | |||||
| cout << F("***SD wiped clean.***\n\n"); | |||||
| } | } | ||||
| } | } | ||||
| // Create a new folder. | // Create a new folder. | ||||
| if (!sd.mkdir("FOLDER1")) error("Create FOLDER1 failed"); | |||||
| cout << pstr("Created FOLDER1\n"); | |||||
| // Create a file in FOLDER1 using a path. | |||||
| if (!file.open("FOLDER1/FILE1.TXT", O_CREAT | O_WRITE)) { | |||||
| error("create FOLDER1/FILE1.TXT failed"); | |||||
| if (!sd.mkdir("Folder1")) { | |||||
| error("Create Folder1 failed"); | |||||
| } | |||||
| cout << F("Created Folder1\n"); | |||||
| // Create a file in Folder1 using a path. | |||||
| if (!file.open("Folder1/file1.txt", O_CREAT | O_WRITE)) { | |||||
| error("create Folder1/file1.txt failed"); | |||||
| } | } | ||||
| file.close(); | file.close(); | ||||
| cout << pstr("Created FOLDER1/FILE1.TXT\n"); | |||||
| // Change volume working directory to FOLDER1. | |||||
| if (!sd.chdir("FOLDER1")) error("chdir failed for FOLDER1.\n"); | |||||
| cout << pstr("chdir to FOLDER1\n"); | |||||
| // Create FILE2.TXT in current directory. | |||||
| if (!file.open("FILE2.TXT", O_CREAT | O_WRITE)) { | |||||
| error("create FILE2.TXT failed"); | |||||
| cout << F("Created Folder1/file1.txt\n"); | |||||
| // Change volume working directory to Folder1. | |||||
| if (!sd.chdir("Folder1")) { | |||||
| error("chdir failed for Folder1.\n"); | |||||
| } | |||||
| cout << F("chdir to Folder1\n"); | |||||
| // Create File2.txt in current directory. | |||||
| if (!file.open("File2.txt", O_CREAT | O_WRITE)) { | |||||
| error("create File2.txt failed"); | |||||
| } | } | ||||
| file.close(); | file.close(); | ||||
| cout << pstr("Created FILE2.TXT in current directory\n"); | |||||
| cout << pstr("List of files on the SD.\n"); | |||||
| cout << F("Created File2.txt in current directory\n"); | |||||
| cout << F("List of files on the SD.\n"); | |||||
| sd.ls("/", LS_R); | sd.ls("/", LS_R); | ||||
| // Remove files from current directory. | // Remove files from current directory. | ||||
| if (!sd.remove("FILE1.TXT") || !sd.remove("FILE2.TXT")) error("remove failed"); | |||||
| cout << pstr("\nFILE1.TXT and FILE2.TXT removed.\n"); | |||||
| if (!sd.remove("file1.txt") || !sd.remove("File2.txt")) { | |||||
| error("remove failed"); | |||||
| } | |||||
| cout << F("\nfile1.txt and File2.txt removed.\n"); | |||||
| // Change current directory to root. | // Change current directory to root. | ||||
| if (!sd.chdir()) error("chdir to root failed.\n"); | |||||
| cout << pstr("List of files on the SD.\n"); | |||||
| if (!sd.chdir()) { | |||||
| error("chdir to root failed.\n"); | |||||
| } | |||||
| cout << F("List of files on the SD.\n"); | |||||
| sd.ls(LS_R); | sd.ls(LS_R); | ||||
| // Remove FOLDER1. | |||||
| if (!sd.rmdir("FOLDER1")) error("rmdir for FOLDER1 failed\n"); | |||||
| cout << pstr("\nFOLDER1 removed, SD empty.\n"); | |||||
| cout << pstr("Done!\n"); | |||||
| // Remove Folder1. | |||||
| if (!sd.rmdir("Folder1")) { | |||||
| error("rmdir for Folder1 failed\n"); | |||||
| } | |||||
| cout << F("\nFolder1 removed, SD empty.\n"); | |||||
| cout << F("Done!\n"); | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // Nothing happens in loop. | // Nothing happens in loop. |
| // Demo of fgets function to read lines from a file. | // Demo of fgets function to read lines from a file. | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // SD chip select pin | // SD chip select pin | ||||
| char line[25]; | char line[25]; | ||||
| int n; | int n; | ||||
| // open test file | // open test file | ||||
| SdFile rdfile("FGETS.TXT", O_READ); | |||||
| SdFile rdfile("fgets.txt", O_READ); | |||||
| // check for open error | // check for open error | ||||
| if (!rdfile.isOpen()) error("demoFgets"); | |||||
| cout << endl << pstr( | |||||
| "Lines with '>' end with a '\\n' character\n" | |||||
| "Lines with '#' do not end with a '\\n' character\n" | |||||
| "\n"); | |||||
| if (!rdfile.isOpen()) { | |||||
| error("demoFgets"); | |||||
| } | |||||
| cout << endl << F( | |||||
| "Lines with '>' end with a '\\n' character\n" | |||||
| "Lines with '#' do not end with a '\\n' character\n" | |||||
| "\n"); | |||||
| // read lines from the file | // read lines from the file | ||||
| while ((n = rdfile.fgets(line, sizeof(line))) > 0) { | while ((n = rdfile.fgets(line, sizeof(line))) > 0) { | ||||
| if (line[n - 1] == '\n') { | if (line[n - 1] == '\n') { | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void makeTestFile() { | void makeTestFile() { | ||||
| // create or open test file | // create or open test file | ||||
| SdFile wrfile("FGETS.TXT", O_WRITE | O_CREAT | O_TRUNC); | |||||
| SdFile wrfile("fgets.txt", O_WRITE | O_CREAT | O_TRUNC); | |||||
| // check for open error | // check for open error | ||||
| if (!wrfile.isOpen()) error("MakeTestFile"); | |||||
| if (!wrfile.isOpen()) { | |||||
| error("MakeTestFile"); | |||||
| } | |||||
| // write test file | // write test file | ||||
| wrfile.print(F( | wrfile.print(F( | ||||
| "Line with CRLF\r\n" | |||||
| "Line with only LF\n" | |||||
| "Long line that will require an extra read\n" | |||||
| "\n" // empty line | |||||
| "Line at EOF without NL" | |||||
| )); | |||||
| "Line with CRLF\r\n" | |||||
| "Line with only LF\n" | |||||
| "Long line that will require an extra read\n" | |||||
| "\n" // empty line | |||||
| "Line at EOF without NL" | |||||
| )); | |||||
| wrfile.close(); | wrfile.close(); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // Wait for Leonardo | while (!Serial) {} // Wait for Leonardo | ||||
| cout << pstr("Type any character to start\n"); | |||||
| cout << F("Type any character to start\n"); | |||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| makeTestFile(); | makeTestFile(); | ||||
| demoFgets(); | demoFgets(); | ||||
| cout << pstr("\nDone\n"); | |||||
| cout << F("\nDone\n"); | |||||
| } | } | ||||
| void loop(void) {} | void loop(void) {} |
| for (int row = 1; row <= max; row++) { | for (int row = 1; row <= max; row++) { | ||||
| for (int col = 1; col <= max; col++) { | for (int col = 1; col <= max; col++) { | ||||
| cout << setw(width) << row * col << (col == max ? '\n' : ' '); | |||||
| cout << setw(width) << row * col << (col == max ? '\n' : ' '); | |||||
| } | } | ||||
| } | } | ||||
| cout << endl; | cout << endl; | ||||
| // shows how to set and restore the fill character | // shows how to set and restore the fill character | ||||
| void showDate(int m, int d, int y) { | void showDate(int m, int d, int y) { | ||||
| // convert two digit year | // convert two digit year | ||||
| if (y < 100) y += 2000; | |||||
| if (y < 100) { | |||||
| y += 2000; | |||||
| } | |||||
| // set new fill to '0' save old fill character | // set new fill to '0' save old fill character | ||||
| char old = cout.fill('0'); | char old = cout.fill('0'); | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void setup(void) { | void setup(void) { | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| delay(2000); | delay(2000); | ||||
| cout << endl << "default formatting" << endl; | cout << endl << "default formatting" << endl; | ||||
| example(); | example(); | ||||
| * Note: This example is meant to demonstrate subtleties the standard and | * Note: This example is meant to demonstrate subtleties the standard and | ||||
| * may not the best way to read a file. | * may not the best way to read a file. | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // SD chip select pin | // SD chip select pin | ||||
| ArduinoOutStream cout(Serial); | ArduinoOutStream cout(Serial); | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void makeTestFile() { | void makeTestFile() { | ||||
| ofstream sdout("GETLINE.TXT"); | |||||
| ofstream sdout("getline.txt"); | |||||
| // use flash for text to save RAM | // use flash for text to save RAM | ||||
| sdout << pstr( | |||||
| "short line\n" | |||||
| "\n" | |||||
| "17 character line\n" | |||||
| "too long for buffer\n" | |||||
| "line with no nl"); | |||||
| sdout << F( | |||||
| "short line\n" | |||||
| "\n" | |||||
| "17 character line\n" | |||||
| "too long for buffer\n" | |||||
| "line with no nl"); | |||||
| sdout.close(); | sdout.close(); | ||||
| } | } | ||||
| void testGetline() { | void testGetline() { | ||||
| const int line_buffer_size = 18; | const int line_buffer_size = 18; | ||||
| char buffer[line_buffer_size]; | char buffer[line_buffer_size]; | ||||
| ifstream sdin("GETLINE.TXT"); | |||||
| ifstream sdin("getline.txt"); | |||||
| int line_number = 0; | int line_number = 0; | ||||
| while (sdin.getline(buffer, line_buffer_size, '\n') || sdin.gcount()) { | while (sdin.getline(buffer, line_buffer_size, '\n') || sdin.gcount()) { | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| // pstr stores strings in flash to save RAM | // pstr stores strings in flash to save RAM | ||||
| cout << pstr("Type any character to start\n"); | |||||
| cout << F("Type any character to start\n"); | |||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // make the test file | // make the test file | ||||
| makeTestFile(); | makeTestFile(); |
| /* | /* | ||||
| * This example reads a simple CSV, comma-separated values, file. | * This example reads a simple CSV, comma-separated values, file. | ||||
| * Each line of the file has three values, a long and two floats. | |||||
| * Each line of the file has a label and three values, a long and two floats. | |||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // SD chip select pin | // SD chip select pin | ||||
| // create Serial stream | // create Serial stream | ||||
| ArduinoOutStream cout(Serial); | ArduinoOutStream cout(Serial); | ||||
| char fileName[] = "TESTFILE.CSV"; | |||||
| char fileName[] = "testfile.csv"; | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // store error strings in flash to save RAM | // store error strings in flash to save RAM | ||||
| #define error(s) sd.errorHalt(F(s)) | #define error(s) sd.errorHalt(F(s)) | ||||
| float f1, f2; | float f1, f2; | ||||
| char text[10]; | char text[10]; | ||||
| char c1, c2, c3; // space for commas. | char c1, c2, c3; // space for commas. | ||||
| // open input file | // open input file | ||||
| ifstream sdin(fileName); | ifstream sdin(fileName); | ||||
| // check for open error | // check for open error | ||||
| if (!sdin.is_open()) error("open"); | |||||
| if (!sdin.is_open()) { | |||||
| error("open"); | |||||
| } | |||||
| // read until input fails | // read until input fails | ||||
| while (1) { | while (1) { | ||||
| // Get text field. | // Get text field. | ||||
| sdin.get(text, sizeof(text), ','); | sdin.get(text, sizeof(text), ','); | ||||
| // Assume EOF if fail. | // Assume EOF if fail. | ||||
| if (sdin.fail()) break; | |||||
| if (sdin.fail()) { | |||||
| break; | |||||
| } | |||||
| // Get commas and numbers. | // Get commas and numbers. | ||||
| sdin >> c1 >> lg >> c2 >> f1 >> c3 >> f2; | sdin >> c1 >> lg >> c2 >> f1 >> c3 >> f2; | ||||
| // Skip CR/LF. | // Skip CR/LF. | ||||
| sdin.skipWhite(); | sdin.skipWhite(); | ||||
| if (sdin.fail()) error("bad input"); | |||||
| if (sdin.fail()) { | |||||
| error("bad input"); | |||||
| } | |||||
| // error in line if not commas | // error in line if not commas | ||||
| if (c1 != ',' || c2 != ',' || c3 != ',') error("comma"); | |||||
| if (c1 != ',' || c2 != ',' || c3 != ',') { | |||||
| error("comma"); | |||||
| } | |||||
| // print in six character wide columns | // print in six character wide columns | ||||
| cout << text << setw(6) << lg << setw(6) << f1 << setw(6) << f2 << endl; | cout << text << setw(6) << lg << setw(6) << f1 << setw(6) << f2 << endl; | ||||
| } | } | ||||
| // Error in an input line if file is not at EOF. | // Error in an input line if file is not at EOF. | ||||
| if (!sdin.eof()) error("readFile"); | |||||
| if (!sdin.eof()) { | |||||
| error("readFile"); | |||||
| } | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // write test file | // write test file | ||||
| // create or open and truncate output file | // create or open and truncate output file | ||||
| ofstream sdout(fileName); | ofstream sdout(fileName); | ||||
| // write file from string stored in flash | // write file from string stored in flash | ||||
| sdout << pstr( | |||||
| "Line 1,1,2.3,4.5\n" | |||||
| "Line 2,6,7.8,9.0\n" | |||||
| "Line 3,9,8.7,6.5\n" | |||||
| "Line 4,-4,-3.2,-1\n") << flush; | |||||
| sdout << F( | |||||
| "Line 1,1,2.3,4.5\n" | |||||
| "Line 2,6,7.8,9.0\n" | |||||
| "Line 3,9,8.7,6.5\n" | |||||
| "Line 4,-4,-3.2,-1\n") << flush; | |||||
| // check for any errors | // check for any errors | ||||
| if (!sdout) error("writeFile"); | |||||
| if (!sdout) { | |||||
| error("writeFile"); | |||||
| } | |||||
| sdout.close(); | sdout.close(); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void setup() { | void setup() { | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| cout << pstr("Type any character to start\n"); | |||||
| cout << F("Type any character to start\n"); | |||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance | // breadboards. use SPI_FULL_SPEED for better performance | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // create test file | // create test file | ||||
| writeFile(); | writeFile(); | ||||
| cout << endl; | cout << endl; | ||||
| // read and print test | // read and print test | ||||
| readFile(); | |||||
| readFile(); | |||||
| cout << "\nDone!" << endl; | cout << "\nDone!" << endl; | ||||
| } | } | ||||
| void loop() {} | void loop() {} |
| /* | /* | ||||
| * This sketch demonstrates use of SdFile::rename() | |||||
| * This program demonstrates use of SdFile::rename() | |||||
| * and SdFat::rename(). | * and SdFat::rename(). | ||||
| */ | */ | ||||
| #include <SPI.h> | |||||
| #include <SPI.h> | |||||
| #include <SdFat.h> | #include <SdFat.h> | ||||
| // SD chip select pin | // SD chip select pin | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) {} // wait for Leonardo | while (!Serial) {} // wait for Leonardo | ||||
| cout << pstr("Insert an empty SD. Type any character to start.") << endl; | |||||
| cout << F("Insert an empty SD. Type any character to start.") << endl; | |||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(400); // catch Due reset problem | delay(400); // catch Due reset problem | ||||
| // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with | ||||
| // breadboards. use SPI_FULL_SPEED for better performance. | // breadboards. use SPI_FULL_SPEED for better performance. | ||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt(); | |||||
| if (!sd.begin(chipSelect, SPI_HALF_SPEED)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| // Remove file/dirs from previous run. | |||||
| if (sd.exists("dir2/DIR3/NAME3.txt")) { | |||||
| cout << F("Removing /dir2/DIR3/NAME3.txt") << endl; | |||||
| if (!sd.remove("dir2/DIR3/NAME3.txt") || | |||||
| !sd.rmdir("dir2/DIR3/") || | |||||
| !sd.rmdir("dir2/")) { | |||||
| error("remove/rmdir failed"); | |||||
| } | |||||
| } | |||||
| // create a file and write one line to the file | // create a file and write one line to the file | ||||
| SdFile file("NAME1.TXT", O_WRITE | O_CREAT); | |||||
| if (!file.isOpen()) error("NAME1"); | |||||
| file.println("A test line for NAME1.TXT"); | |||||
| SdFile file("Name1.txt", O_WRITE | O_CREAT); | |||||
| if (!file.isOpen()) { | |||||
| error("Name1.txt"); | |||||
| } | |||||
| file.println("A test line for Name1.txt"); | |||||
| // rename the file NAME2.TXT and add a line. | |||||
| // rename the file name2.txt and add a line. | |||||
| // sd.vwd() is the volume working directory, root. | // sd.vwd() is the volume working directory, root. | ||||
| if (!file.rename(sd.vwd(), "NAME2.TXT")) error("NAME2"); | |||||
| file.println("A test line for NAME2.TXT"); | |||||
| if (!file.rename(sd.vwd(), "name2.txt")) { | |||||
| error("name2.txt"); | |||||
| } | |||||
| file.println("A test line for name2.txt"); | |||||
| // list files | // list files | ||||
| cout << pstr("------") << endl; | |||||
| cout << F("------") << endl; | |||||
| sd.ls(LS_R); | sd.ls(LS_R); | ||||
| // make a new directory - "DIR1" | |||||
| if (!sd.mkdir("DIR1")) error("DIR1"); | |||||
| // make a new directory - "Dir1" | |||||
| if (!sd.mkdir("Dir1")) { | |||||
| error("Dir1"); | |||||
| } | |||||
| // move file into DIR1, rename it NAME3.TXT and add a line | |||||
| if (!file.rename(sd.vwd(), "DIR1/NAME3.TXT")) error("NAME3"); | |||||
| file.println("A line for DIR1/NAME3.TXT"); | |||||
| // move file into Dir1, rename it NAME3.txt and add a line | |||||
| if (!file.rename(sd.vwd(), "Dir1/NAME3.txt")) { | |||||
| error("NAME3.txt"); | |||||
| } | |||||
| file.println("A line for Dir1/NAME3.txt"); | |||||
| // list files | // list files | ||||
| cout << pstr("------") << endl; | |||||
| cout << F("------") << endl; | |||||
| sd.ls(LS_R); | sd.ls(LS_R); | ||||
| // make directory "DIR2" | |||||
| if (!sd.mkdir("DIR2")) error("DIR2"); | |||||
| // make directory "dir2" | |||||
| if (!sd.mkdir("dir2")) { | |||||
| error("dir2"); | |||||
| } | |||||
| // close file before rename(oldPath, newPath) | // close file before rename(oldPath, newPath) | ||||
| file.close(); | file.close(); | ||||
| // move DIR1 into DIR2 and rename it DIR3 | |||||
| if (!sd.rename("DIR1", "DIR2/DIR3")) error("DIR2/DIR3"); | |||||
| // move Dir1 into dir2 and rename it DIR3 | |||||
| if (!sd.rename("Dir1", "dir2/DIR3")) { | |||||
| error("dir2/DIR3"); | |||||
| } | |||||
| // open file for append in new location and add a line | // open file for append in new location and add a line | ||||
| if (!file.open("DIR2/DIR3/NAME3.TXT", O_WRITE | O_APPEND)) { | |||||
| error("DIR2/DIR3/NAME3.TXT"); | |||||
| if (!file.open("dir2/DIR3/NAME3.txt", O_WRITE | O_APPEND)) { | |||||
| error("dir2/DIR3/NAME3.txt"); | |||||
| } | } | ||||
| file.println("A line for DIR2/DIR3/NAME3.TXT"); | |||||
| file.println("A line for dir2/DIR3/NAME3.txt"); | |||||
| file.close(); | |||||
| // list files | // list files | ||||
| cout << pstr("------") << endl; | |||||
| cout << F("------") << endl; | |||||
| sd.ls(LS_R); | sd.ls(LS_R); | ||||
| cout << pstr("Done") << endl; | |||||
| cout << F("Done") << endl; | |||||
| } | } | ||||
| void loop() {} | void loop() {} |
| while (1) { | while (1) { | ||||
| t = millis(); | t = millis(); | ||||
| while (!m_hw->available()) { | while (!m_hw->available()) { | ||||
| if ((millis() - t) > 10) goto done; | |||||
| if ((millis() - t) > 10) { | |||||
| goto done; | |||||
| } | |||||
| } | } | ||||
| if (i >= (m_size - 1)) { | if (i >= (m_size - 1)) { | ||||
| setstate(failbit); | setstate(failbit); | ||||
| m_line[i++] = m_hw->read(); | m_line[i++] = m_hw->read(); | ||||
| m_line[i] = '\0'; | m_line[i] = '\0'; | ||||
| } | } | ||||
| done: | |||||
| done: | |||||
| init(m_line); | init(m_line); | ||||
| } | } | ||||
| * \param[in] way | * \param[in] way | ||||
| * \return true/false. | * \return true/false. | ||||
| */ | */ | ||||
| bool seekoff(off_type off, seekdir way) {return false;} | |||||
| /** Internal - do not use. | |||||
| * \param[in] pos | |||||
| * \return true/false. | |||||
| */ | |||||
| bool seekpos(pos_type pos) {return false;} | |||||
| bool seekoff(off_type off, seekdir way) { | |||||
| return false; | |||||
| } | |||||
| /** Internal - do not use. | |||||
| * \param[in] pos | |||||
| * \return true/false. | |||||
| */ | |||||
| bool seekpos(pos_type pos) { | |||||
| return false; | |||||
| } | |||||
| private: | private: | ||||
| char *m_line; | char *m_line; | ||||
| * \param[in] c | * \param[in] c | ||||
| */ | */ | ||||
| void putch(char c) { | void putch(char c) { | ||||
| if (c == '\n') m_pr->write('\r'); | |||||
| if (c == '\n') { | |||||
| m_pr->write('\r'); | |||||
| } | |||||
| m_pr->write(c); | m_pr->write(c); | ||||
| } | } | ||||
| void putstr(const char* str) {m_pr->write(str);} | |||||
| bool seekoff(off_type off, seekdir way) {return false;} | |||||
| bool seekpos(pos_type pos) {return false;} | |||||
| bool sync() {return true;} | |||||
| pos_type tellpos() {return 0;} | |||||
| void putstr(const char* str) { | |||||
| m_pr->write(str); | |||||
| } | |||||
| bool seekoff(off_type off, seekdir way) { | |||||
| return false; | |||||
| } | |||||
| bool seekpos(pos_type pos) { | |||||
| return false; | |||||
| } | |||||
| bool sync() { | |||||
| return true; | |||||
| } | |||||
| pos_type tellpos() { | |||||
| return 0; | |||||
| } | |||||
| /// @endcond | /// @endcond | ||||
| private: | private: | ||||
| ArduinoOutStream() {} | ArduinoOutStream() {} |
| fastDigitalWrite(pin, !fastDigitalRead(pin)); | fastDigitalWrite(pin, !fastDigitalRead(pin)); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| inline void fastPinMode(uint8_t pin, bool mode) {pinMode(pin, mode);} | |||||
| inline void fastPinMode(uint8_t pin, bool mode) { | |||||
| pinMode(pin, mode); | |||||
| } | |||||
| #else // __arm__ | #else // __arm__ | ||||
| #include <avr/io.h> | #include <avr/io.h> | ||||
| #include <util/atomic.h> | #include <util/atomic.h> | ||||
| //============================================================================== | //============================================================================== | ||||
| /** generate bad pin number error */ | /** generate bad pin number error */ | ||||
| void badPinNumber(void) | void badPinNumber(void) | ||||
| __attribute__((error("Pin number is too large or not a constant"))); | |||||
| __attribute__((error("Pin number is too large or not a constant"))); | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** Check for valid pin number | /** Check for valid pin number | ||||
| * @param[in] pin Number of pin to be checked. | * @param[in] pin Number of pin to be checked. | ||||
| static inline __attribute__((always_inline)) | static inline __attribute__((always_inline)) | ||||
| void badPinCheck(uint8_t pin) { | void badPinCheck(uint8_t pin) { | ||||
| if (!__builtin_constant_p(pin) || pin >= digitalPinCount) { | if (!__builtin_constant_p(pin) || pin >= digitalPinCount) { | ||||
| badPinNumber(); | |||||
| badPinNumber(); | |||||
| } | } | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| static inline __attribute__((always_inline)) | static inline __attribute__((always_inline)) | ||||
| void fastDigitalToggle(uint8_t pin) { | void fastDigitalToggle(uint8_t pin) { | ||||
| badPinCheck(pin); | badPinCheck(pin); | ||||
| if (pinMap[pin].pin > reinterpret_cast<uint8_t*>(0X5F)) { | |||||
| // must write bit to high address port | |||||
| *pinMap[pin].pin = 1 << pinMap[pin].bit; | |||||
| } else { | |||||
| // will compile to sbi and PIN register will not be read. | |||||
| *pinMap[pin].pin |= 1 << pinMap[pin].bit; | |||||
| } | |||||
| if (pinMap[pin].pin > reinterpret_cast<uint8_t*>(0X5F)) { | |||||
| // must write bit to high address port | |||||
| *pinMap[pin].pin = 1 << pinMap[pin].bit; | |||||
| } else { | |||||
| // will compile to sbi and PIN register will not be read. | |||||
| *pinMap[pin].pin |= 1 << pinMap[pin].bit; | |||||
| } | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** Set pin value | /** Set pin value | ||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** set pin configuration | /** set pin configuration | ||||
| * @param[in] mode If true set output mode else input mode | * @param[in] mode If true set output mode else input mode | ||||
| * @param[in] level If mode is output, set level high/low. If mode | |||||
| * @param[in] level If mode is output, set level high/low. If mode | |||||
| * is input, enable or disable the pin's 20K pull-up. | * is input, enable or disable the pin's 20K pull-up. | ||||
| */ | */ | ||||
| inline __attribute__((always_inline)) | inline __attribute__((always_inline)) | ||||
| * Set pin level high if output mode or enable 20K pull-up if input mode. | * Set pin level high if output mode or enable 20K pull-up if input mode. | ||||
| */ | */ | ||||
| inline __attribute__((always_inline)) | inline __attribute__((always_inline)) | ||||
| void high() {write(true);} | |||||
| void high() { | |||||
| write(true); | |||||
| } | |||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** | /** | ||||
| * Set pin level low if output mode or disable 20K pull-up if input mode. | * Set pin level low if output mode or disable 20K pull-up if input mode. | ||||
| */ | */ | ||||
| inline __attribute__((always_inline)) | inline __attribute__((always_inline)) | ||||
| void low() {write(false);} | |||||
| void low() { | |||||
| write(false); | |||||
| } | |||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** | /** | ||||
| * Set pin mode | * Set pin mode |
| // FatFile class static and const definitions | // FatFile class static and const definitions | ||||
| // flags for ls() | // flags for ls() | ||||
| /** ls() flag to print modify date */ | |||||
| uint8_t const LS_DATE = 1; | |||||
| /** ls() flag to print file size */ | |||||
| uint8_t const LS_SIZE = 2; | |||||
| /** ls() flag for list all files including hidden. */ | |||||
| uint8_t const LS_A = 1; | |||||
| /** ls() flag to print modify. date */ | |||||
| uint8_t const LS_DATE = 2; | |||||
| /** ls() flag to print file size. */ | |||||
| uint8_t const LS_SIZE = 4; | |||||
| /** ls() flag for recursive list of subdirectories */ | /** ls() flag for recursive list of subdirectories */ | ||||
| uint8_t const LS_R = 4; | |||||
| uint8_t const LS_R = 8; | |||||
| // flags for timestamp | // flags for timestamp | ||||
| /** set the file's last access date */ | /** set the file's last access date */ |
| * \file | * \file | ||||
| * \brief FatFile class | * \brief FatFile class | ||||
| */ | */ | ||||
| #include <ctype.h> | |||||
| // #include <ctype.h> | |||||
| #include <string.h> | #include <string.h> | ||||
| #include <stddef.h> | #include <stddef.h> | ||||
| #include <limits.h> | #include <limits.h> | ||||
| #include "FatVolume.h" | #include "FatVolume.h" | ||||
| class FatFileSystem; | class FatFileSystem; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| #if defined(ARDUINO) || defined(DOXYGEN) | |||||
| #include <Arduino.h> | |||||
| /** Use Print on Arduino */ | |||||
| typedef Print print_t; | |||||
| #else // ARDUINO | |||||
| // Arduino type for flash string. | |||||
| class __FlashStringHelper; | |||||
| /** | |||||
| * \class CharWriter | |||||
| * \brief Character output - often serial port. | |||||
| */ | |||||
| class CharWriter { | |||||
| public: | |||||
| virtual size_t write(char c) = 0; | |||||
| virtual size_t write(const char* s) = 0; | |||||
| }; | |||||
| typedef Print print_t; | |||||
| #endif // ARDUINO | |||||
| //------------------------------------------------------------------------------ | |||||
| // Stuff to store strings in AVR flash. | // Stuff to store strings in AVR flash. | ||||
| #ifdef __AVR__ | #ifdef __AVR__ | ||||
| #include <avr/pgmspace.h> | #include <avr/pgmspace.h> | ||||
| uint32_t cluster; | uint32_t cluster; | ||||
| FatPos_t() : position(0), cluster(0) {} | FatPos_t() : position(0), cluster(0) {} | ||||
| }; | }; | ||||
| //------------------------------------------------------------------------------ | |||||
| /** Expression for path name separator. */ | |||||
| #define isDirSeparator(c) ((c) == '/') | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * \struct fname_t | |||||
| * \brief Internal type for Short File Name - do not use in user apps. | |||||
| */ | |||||
| struct fname_t { | |||||
| /** Flags for base and extension character case and LFN. */ | |||||
| uint8_t flags; | |||||
| /** length of Long File Name */ | |||||
| size_t len; | |||||
| /** Long File Name start. */ | |||||
| const char* lfn; | |||||
| /** position for sequence number */ | |||||
| uint8_t seqPos; | |||||
| /** Short File Name */ | |||||
| uint8_t sfn[11]; | |||||
| }; | |||||
| /** Derived from a LFN with loss or conversion of characters. */ | |||||
| const uint8_t FNAME_FLAG_LOST_CHARS = 0X01; | |||||
| /** Base-name or extension has mixed case. */ | |||||
| const uint8_t FNAME_FLAG_MIXED_CASE = 0X02; | |||||
| /** LFN entries are required for file name. */ | |||||
| const uint8_t FNAME_FLAG_NEED_LFN = | |||||
| FNAME_FLAG_LOST_CHARS | FNAME_FLAG_MIXED_CASE; | |||||
| /** Filename base-name is all lower case */ | |||||
| const uint8_t FNAME_FLAG_LC_BASE = DIR_NT_LC_BASE; | |||||
| /** Filename extension is all lower case. */ | |||||
| const uint8_t FNAME_FLAG_LC_EXT = DIR_NT_LC_EXT; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| * \class FatFile | * \class FatFile | ||||
| */ | */ | ||||
| class FatFile { | class FatFile { | ||||
| public: | public: | ||||
| #ifndef DOXYGEN_SHOULD_SKIP_THIS | |||||
| // development tests - DO NOT USE! | |||||
| bool createLfn(FatFile* dirFile, //////////////////////////////////////////////////// | |||||
| uint16_t bgnIndex, char* name, uint8_t oflag); /////////////////////// | |||||
| bool findLfn(const char* name, uint16_t* bgnIndex, uint16_t *endIndex); ///////////// | |||||
| bool findSfn(uint8_t* sfn, uint16_t* index); //////////////////////////////////////// | |||||
| #endif // DOXYGEN_SHOULD_SKIP_THIS | |||||
| /** Create an instance. */ | /** Create an instance. */ | ||||
| FatFile() : m_writeError(false), m_attr(FILE_ATTR_CLOSED) {} | |||||
| FatFile() : m_attr(FILE_ATTR_CLOSED), m_error(0) {} | |||||
| /** Create a file object and open it in the current working directory. | /** Create a file object and open it in the current working directory. | ||||
| * | * | ||||
| * \param[in] path A path with a valid 8.3 DOS name for a file to be opened. | * \param[in] path A path with a valid 8.3 DOS name for a file to be opened. | ||||
| */ | */ | ||||
| FatFile(const char* path, uint8_t oflag) { | FatFile(const char* path, uint8_t oflag) { | ||||
| m_attr = FILE_ATTR_CLOSED; | m_attr = FILE_ATTR_CLOSED; | ||||
| m_writeError = false; | |||||
| m_error = 0; | |||||
| open(path, oflag); | open(path, oflag); | ||||
| } | } | ||||
| #if DESTRUCTOR_CLOSES_FILE | #if DESTRUCTOR_CLOSES_FILE | ||||
| ~FatFile() {if(isOpen()) close();} | |||||
| ~FatFile() { | |||||
| if (isOpen()) { | |||||
| close(); | |||||
| } | |||||
| } | |||||
| #endif // DESTRUCTOR_CLOSES_FILE | #endif // DESTRUCTOR_CLOSES_FILE | ||||
| /** \return value of writeError */ | /** \return value of writeError */ | ||||
| bool getWriteError() {return m_writeError;} | |||||
| bool getWriteError() { | |||||
| return m_error & WRITE_ERROR; | |||||
| } | |||||
| /** Set writeError to zero */ | /** Set writeError to zero */ | ||||
| void clearWriteError() {m_writeError = 0;} | |||||
| //---------------------------------------------------------------------------- | |||||
| // helpers for stream classes | |||||
| void clearWriteError() { | |||||
| m_error &= ~WRITE_ERROR; | |||||
| } | |||||
| /** Clear all error bits. */ | |||||
| void clearError() { | |||||
| m_error = 0; | |||||
| } | |||||
| /** \return All error bits. */ | |||||
| uint8_t getError() { | |||||
| return m_error; | |||||
| } | |||||
| /** get position for streams | /** get position for streams | ||||
| * \param[out] pos struct to receive position | * \param[out] pos struct to receive position | ||||
| */ | */ | ||||
| * \param[out] pos struct with value for new position | * \param[out] pos struct with value for new position | ||||
| */ | */ | ||||
| void setpos(FatPos_t* pos); | void setpos(FatPos_t* pos); | ||||
| //---------------------------------------------------------------------------- | |||||
| /** \return number of bytes available from the current position to EOF */ | |||||
| uint32_t available() {return fileSize() - curPosition();} | |||||
| /** \return The number of bytes available from the current position | |||||
| * to EOF for normal files. Zero is returned for directory files. | |||||
| */ | |||||
| uint32_t available() { | |||||
| return isFile() ? fileSize() - curPosition() : 0; | |||||
| } | |||||
| /** Close a file and force cached data and directory information | /** Close a file and force cached data and directory information | ||||
| * to be written to the storage device. | * 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. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool close(); | bool close(); | ||||
| /** Check for contiguous file and return its raw block range. | /** Check for contiguous file and return its raw block range. | ||||
| * \param[out] bgnBlock the first block address for the file. | * \param[out] bgnBlock the first block address for the file. | ||||
| * \param[out] endBlock the last 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. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock); | bool contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock); | ||||
| /** Create and open a new contiguous file of a specified size. | /** Create and open a new contiguous file of a specified size. | ||||
| * \param[in] path A path with a valid DOS 8.3 file name. | * \param[in] path A path with a valid DOS 8.3 file name. | ||||
| * \param[in] size The desired file size. | * \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 path 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. | |||||
| * | |||||
| * \return The value true is returned for success and | |||||
| * the value false, is returned for failure. | |||||
| */ | */ | ||||
| bool createContiguous(FatFile* dirFile, | bool createContiguous(FatFile* dirFile, | ||||
| const char* path, uint32_t size); | |||||
| const char* path, uint32_t size); | |||||
| /** \return The current cluster number for a file or directory. */ | /** \return The current cluster number for a file or directory. */ | ||||
| uint32_t curCluster() const {return m_curCluster;} | |||||
| uint32_t curCluster() const { | |||||
| return m_curCluster; | |||||
| } | |||||
| /** \return The current position for a file or directory. */ | /** \return The current position for a file or directory. */ | ||||
| uint32_t curPosition() const {return m_curPosition;} | |||||
| uint32_t curPosition() const { | |||||
| return m_curPosition; | |||||
| } | |||||
| /** \return Current working directory */ | /** \return Current working directory */ | ||||
| static FatFile* cwd() {return m_cwd;} | |||||
| static FatFile* cwd() { | |||||
| return m_cwd; | |||||
| } | |||||
| /** Set the date/time callback function | /** Set the date/time callback function | ||||
| * | * | ||||
| * \param[in] dateTime The user's call back function. The callback | * \param[in] dateTime The user's call back function. The callback | ||||
| m_dateTime = dateTime; | m_dateTime = dateTime; | ||||
| } | } | ||||
| /** Cancel the date/time callback function. */ | /** Cancel the date/time callback function. */ | ||||
| static void dateTimeCallbackCancel() {m_dateTime = 0;} | |||||
| static void dateTimeCallbackCancel() { | |||||
| m_dateTime = 0; | |||||
| } | |||||
| /** Return a file's directory entry. | /** Return a file's directory entry. | ||||
| * | * | ||||
| * \param[out] dir Location for return of the file's directory entry. | * \param[out] dir Location for return of the file's directory entry. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool dirEntry(dir_t* dir); | bool dirEntry(dir_t* dir); | ||||
| /** | /** | ||||
| * \return The index of this file in it's directory. | * \return The index of this file in it's directory. | ||||
| */ | */ | ||||
| uint16_t dirIndex() {return m_dirIndex;} | |||||
| uint16_t dirIndex() { | |||||
| return m_dirIndex; | |||||
| } | |||||
| /** Format the name field of \a dir into the 13 byte array | /** Format the name field of \a dir into the 13 byte array | ||||
| * \a name in standard 8.3 short name format. | * \a name in standard 8.3 short name format. | ||||
| * | * | ||||
| * \return length of the name. | * \return length of the name. | ||||
| */ | */ | ||||
| static uint8_t dirName(const dir_t* dir, char* name); | static uint8_t dirName(const dir_t* dir, char* name); | ||||
| /** \return The number of bytes allocated to a directory or zero | |||||
| * if an error occurs. | |||||
| */ | |||||
| uint32_t dirSize(); | |||||
| /** Dump file in Hex | |||||
| * \param[in] pr Print stream for list. | |||||
| * \param[in] pos Start position in file. | |||||
| * \param[in] n number of locations to dump. | |||||
| */ | |||||
| void dmpFile(print_t* pr, uint32_t pos, size_t n); | |||||
| /** Test for the existence of a file in a directory | /** Test for the existence of a file in a directory | ||||
| * | * | ||||
| * \param[in] path Path of the file to be tested for. | * \param[in] path Path of the file to be tested for. | ||||
| * If no data is read, fgets() returns zero for EOF or -1 if an error occurred. | * If no data is read, fgets() returns zero for EOF or -1 if an error occurred. | ||||
| */ | */ | ||||
| int16_t fgets(char* str, int16_t num, char* delim = 0); | int16_t fgets(char* str, int16_t num, char* delim = 0); | ||||
| /** \return The total number of bytes in a file or directory. */ | |||||
| uint32_t fileSize() const {return m_fileSize;} | |||||
| /** \return The total number of bytes in a file. */ | |||||
| uint32_t fileSize() const { | |||||
| return m_fileSize; | |||||
| } | |||||
| /** \return The first cluster number for a file or directory. */ | /** \return The first cluster number for a file or directory. */ | ||||
| uint32_t firstCluster() const {return m_firstCluster;} | |||||
| /** Get a file's name | |||||
| * | |||||
| * \param[out] name An array of 13 characters for the file's name. | |||||
| uint32_t firstCluster() const { | |||||
| return m_firstCluster; | |||||
| } | |||||
| /** | |||||
| * Get a file's name followed by a zero byte. | |||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \param[out] name An array of characters for the file's name. | |||||
| * \param[in] size The size of the array in bytes. The array | |||||
| * must be at least 13 bytes long. The file name will be | |||||
| * truncated if it is too long. | |||||
| * \return The value true, is returned for success and | |||||
| * the value false, is returned for failure. | |||||
| */ | */ | ||||
| bool getFilename(char* name); | |||||
| bool getFilename(char* name, size_t size); | |||||
| /** | |||||
| * Get a file's Short File Name followed by a zero byte. | |||||
| * | |||||
| * \param[out] name An array of characters for the file's name. | |||||
| * The array must be at least 13 bytes long. | |||||
| * \return The value true, is returned for success and | |||||
| * the value false, is returned for failure. | |||||
| */ | |||||
| bool getSFN(char* name); | |||||
| /** \return True if this is a directory else false. */ | /** \return True if this is a directory else false. */ | ||||
| bool isDir() const {return m_attr & FILE_ATTR_DIR;} | |||||
| bool isDir() const { | |||||
| return m_attr & FILE_ATTR_DIR; | |||||
| } | |||||
| /** \return True if this is a normal file else false. */ | /** \return True if this is a normal file else false. */ | ||||
| bool isFile() const {return !isDir();} | |||||
| bool isFile() const { | |||||
| return m_attr & FILE_ATTR_FILE; | |||||
| } | |||||
| /** \return True if this is a hidden file else false. */ | /** \return True if this is a hidden file else false. */ | ||||
| bool isHidden() const {return m_attr & FILE_ATTR_HIDDEN;} | |||||
| bool isHidden() const { | |||||
| return m_attr & FILE_ATTR_HIDDEN; | |||||
| } | |||||
| /** \return true if this file has a Long File Name. */ | |||||
| bool isLFN() const { | |||||
| return m_lfnOrd; | |||||
| } | |||||
| /** \return True if this is an open file/directory else false. */ | /** \return True if this is an open file/directory else false. */ | ||||
| bool isOpen() const {return m_attr & FILE_ATTR_IS_OPEN;} | |||||
| bool isOpen() const { | |||||
| return m_attr; | |||||
| } | |||||
| /** \return True if this is the root directory. */ | /** \return True if this is the root directory. */ | ||||
| bool isRoot() const {return m_attr & FILE_ATTR_ROOT;} | |||||
| /** \return True if file is read-only */ | |||||
| bool isReadOnly() const {return m_attr & FILE_ATTR_READ_ONLY;} | |||||
| bool isRoot() const { | |||||
| return m_attr & FILE_ATTR_ROOT; | |||||
| } | |||||
| /** \return True if this is the FAT32 root directory. */ | |||||
| bool isRoot32() const { | |||||
| return m_attr & FILE_ATTR_ROOT32; | |||||
| } | |||||
| /** \return True if this is the FAT12 of FAT16 root directory. */ | /** \return True if this is the FAT12 of FAT16 root directory. */ | ||||
| bool isRootFixed() const {return m_attr & FILE_ATTR_ROOT_FIXED;} | |||||
| bool isRootFixed() const { | |||||
| return m_attr & FILE_ATTR_ROOT_FIXED; | |||||
| } | |||||
| /** \return True if file is read-only */ | |||||
| bool isReadOnly() const { | |||||
| return m_attr & FILE_ATTR_READ_ONLY; | |||||
| } | |||||
| /** \return True if this is a subdirectory else false. */ | /** \return True if this is a subdirectory else false. */ | ||||
| bool isSubDir() const {return m_attr & FILE_ATTR_SUBDIR;} | |||||
| bool isSubDir() const { | |||||
| return m_attr & FILE_ATTR_SUBDIR; | |||||
| } | |||||
| /** \return True if this is a system file else false. */ | /** \return True if this is a system file else false. */ | ||||
| bool isSystem() const {return m_attr & FILE_ATTR_SYSTEM;} | |||||
| bool isSystem() const { | |||||
| return m_attr & FILE_ATTR_SYSTEM; | |||||
| } | |||||
| /** Check for a legal 8.3 character. | |||||
| * \param[in] c Character to be checked. | |||||
| * \return true for a legal 8.3 character else false. | |||||
| */ | |||||
| static bool legal83Char(uint8_t c) { | |||||
| if (c == '"' || c == '|') { | |||||
| return false; | |||||
| } | |||||
| // *+,./ | |||||
| if (0X2A <= c && c <= 0X2F && c != 0X2D) { | |||||
| return false; | |||||
| } | |||||
| // :;<=>? | |||||
| if (0X3A <= c && c <= 0X3F) { | |||||
| return false; | |||||
| } | |||||
| // [\] | |||||
| if (0X5B <= c && c <= 0X5D) { | |||||
| return false; | |||||
| } | |||||
| return 0X20 < c && c < 0X7F; | |||||
| } | |||||
| /** List directory contents. | /** List directory contents. | ||||
| * | * | ||||
| * \param[in] pr Print stream for list. | * \param[in] pr Print stream for list. | ||||
| * | * | ||||
| * \param[in] pFlag Create missing parent directories if true. | * \param[in] pFlag Create missing parent directories if true. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * Reasons for failure include this file is already open, \a parent is not a | |||||
| * directory, \a path is invalid or already exists in \a parent. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool mkdir(FatFile* dir, const char* path, bool pFlag = true); | bool mkdir(FatFile* dir, const char* path, bool pFlag = true); | ||||
| /** Open a file in the volume working directory of a FatFileSystem. | /** Open a file in the volume working directory of a FatFileSystem. | ||||
| * \param[in] oflag bitwise-inclusive OR of open mode flags. | * \param[in] oflag bitwise-inclusive OR of open mode flags. | ||||
| * See see FatFile::open(FatFile*, const char*, uint8_t). | * See see FatFile::open(FatFile*, const char*, uint8_t). | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool open(FatFileSystem* fs, const char* path, uint8_t oflag); | bool open(FatFileSystem* fs, const char* path, uint8_t oflag); | ||||
| /** Open a file by index. | /** Open a file by index. | ||||
| * \note Directory files must be opened read only. Write and truncation is | * \note Directory files must be opened read only. Write and truncation is | ||||
| * not allowed for directory files. | * 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 file is already open, \a dirFile is not | |||||
| * a directory, \a path is invalid, the file does not exist | |||||
| * or can't be opened in the access mode specified by oflag. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool open(FatFile* dirFile, const char* path, uint8_t oflag); | bool open(FatFile* dirFile, const char* path, uint8_t oflag); | ||||
| /** Open a file in the current working directory. | /** Open a file in the current working directory. | ||||
| * \param[in] oflag bitwise-inclusive OR of open mode flags. | * \param[in] oflag bitwise-inclusive OR of open mode flags. | ||||
| * See see FatFile::open(FatFile*, const char*, uint8_t). | * See see FatFile::open(FatFile*, const char*, uint8_t). | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool open(const char* path, uint8_t oflag = O_READ) { | bool open(const char* path, uint8_t oflag = O_READ) { | ||||
| return open(m_cwd, path, oflag); | return open(m_cwd, path, oflag); | ||||
| * \return true for success or false for failure. | * \return true for success or false for failure. | ||||
| */ | */ | ||||
| bool openNext(FatFile* dirFile, uint8_t oflag = O_READ); | bool openNext(FatFile* dirFile, uint8_t oflag = O_READ); | ||||
| /** Open the next file or subdirectory in a directory and return the | |||||
| * file name. The Long %File Name, LFN, is returned if present otherwise | |||||
| * the 8.3 short name will be returned. | |||||
| * | |||||
| * \param[in] dirFile An open FatFile instance for the directory | |||||
| * containing the file to be opened. | |||||
| * | |||||
| * \param[out] name Location that will receive the name. | |||||
| * | |||||
| * \param[in] size The size of the name array. | |||||
| * | |||||
| * \param[in] oflag bitwise-inclusive OR of open mode flags. | |||||
| * See see FatFile::open(FatFile*, const char*, uint8_t). | |||||
| * | |||||
| * \return For success, the length of the returned name. The name | |||||
| * will be truncated to size - 1 bytes followed by a zero byte. | |||||
| * For EOF, zero will be returned. If an error occurs, -1 is | |||||
| * returned. | |||||
| */ | |||||
| int openNextLFN(FatFile* dirFile, char* name, size_t size, uint8_t oflag); | |||||
| /** Open a volume's root directory. | /** Open a volume's root directory. | ||||
| * | * | ||||
| * \param[in] vol The FAT volume containing the root directory to be opened. | * \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 file is already open, the FAT volume has | |||||
| * not been initialized or it a FAT12 volume. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool openRoot(FatVolume* vol); | bool openRoot(FatVolume* vol); | ||||
| /** Return the next available byte without consuming it. | /** Return the next available byte without consuming it. | ||||
| * | * | ||||
| * \param[in] pr Print stream for output. | * \param[in] pr Print stream for output. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool printCreateDateTime(print_t* pr); | bool printCreateDateTime(print_t* pr); | ||||
| /** %Print a directory date field. | /** %Print a directory date field. | ||||
| * | * | ||||
| * \param[in] pr Print stream for output. | * \param[in] pr Print stream for output. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool printModifyDateTime(print_t* pr); | bool printModifyDateTime(print_t* pr); | ||||
| /** Print a file's name | /** Print a file's name | ||||
| * | * | ||||
| * \param[in] pr Print stream for output. | * \param[in] pr Print stream for output. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| size_t printName(print_t* pr); | size_t printName(print_t* pr); | ||||
| /** Print a file's size. | /** Print a file's size. | ||||
| * | * | ||||
| * \param[in] pr Print stream for output. | * \param[in] pr Print stream for output. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The number of characters printed is returned | |||||
| * for success and zero is returned for failure. | |||||
| */ | */ | ||||
| size_t printFileSize(print_t* pr); | size_t printFileSize(print_t* pr); | ||||
| /** Print a file's Short File Name. | |||||
| * | |||||
| * \param[in] pr Print stream for output. | |||||
| * | |||||
| * \return The number of characters printed is returned | |||||
| * for success and zero is returned for failure. | |||||
| */ | |||||
| size_t printSFN(print_t* pr); | |||||
| /** Read the next byte from a file. | /** Read the next byte from a file. | ||||
| * | * | ||||
| * \return For success read returns the next byte in the file as an int. | * \return For success read returns the next byte in the file as an int. | ||||
| * file that has a long name. For example if a file has the long name | * 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". | * "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. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool remove(); | bool remove(); | ||||
| /** Remove a file. | /** Remove a file. | ||||
| * file that has a long name. For example if a file has the long name | * 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". | * "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 path is not found | |||||
| * or an I/O error occurred. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| static bool remove(FatFile* dirFile, const char* path); | static bool remove(FatFile* dirFile, const char* path); | ||||
| /** Set the file's current position to zero. */ | /** Set the file's current position to zero. */ | ||||
| void rewind() {seekSet(0);} | |||||
| void rewind() { | |||||
| seekSet(0); | |||||
| } | |||||
| /** Rename a file or subdirectory. | /** Rename a file or subdirectory. | ||||
| * | * | ||||
| * \param[in] dirFile Directory for the new path. | * \param[in] dirFile Directory for the new path. | ||||
| * \param[in] newPath New path name for the file/directory. | * \param[in] newPath New path name for the file/directory. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * Reasons for failure include \a dirFile is not open or is not a directory | |||||
| * file, newPath is invalid or already exists, or an I/O error occurs. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool rename(FatFile* dirFile, const char* newPath); | bool rename(FatFile* dirFile, const char* newPath); | ||||
| /** Remove a directory file. | /** Remove a directory file. | ||||
| * directory that has a long name. For example if a directory has the | * 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". | * 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. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool rmdir(); | bool rmdir(); | ||||
| /** Recursively delete a directory and all contained files. | /** Recursively delete a directory and all contained files. | ||||
| * \note This function should not be used to delete the 8.3 version of | * \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(). | * 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. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool rmRfStar(); | bool rmRfStar(); | ||||
| /** Set the files position to current position + \a pos. See seekSet(). | /** Set the files position to current position + \a pos. See seekSet(). | ||||
| return seekSet(m_curPosition + offset); | return seekSet(m_curPosition + offset); | ||||
| } | } | ||||
| /** Set the files position to end-of-file + \a offset. See seekSet(). | /** Set the files position to end-of-file + \a offset. See seekSet(). | ||||
| * Can't be used for directory files since file size is not defined. | |||||
| * \param[in] offset The new position in bytes from end-of-file. | * \param[in] offset The new position in bytes from end-of-file. | ||||
| * \return true for success or false for failure. | * \return true for success or false for failure. | ||||
| */ | */ | ||||
| bool seekEnd(int32_t offset = 0) {return seekSet(m_fileSize + offset);} | |||||
| bool seekEnd(int32_t offset = 0) { | |||||
| return isFile() ? seekSet(m_fileSize + offset) : false; | |||||
| } | |||||
| /** Sets a file's position. | /** Sets a file's position. | ||||
| * | * | ||||
| * \param[in] pos The new position in bytes from the beginning of the file. | * \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. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool seekSet(uint32_t pos); | bool seekSet(uint32_t pos); | ||||
| /** Set the current working directory. | /** Set the current working directory. | ||||
| * \return true for success else false. | * \return true for success else false. | ||||
| */ | */ | ||||
| static bool setCwd(FatFile* dir) { | static bool setCwd(FatFile* dir) { | ||||
| if (!dir->isDir()) return false; | |||||
| if (!dir->isDir()) { | |||||
| return false; | |||||
| } | |||||
| m_cwd = dir; | m_cwd = dir; | ||||
| return true; | return true; | ||||
| } | } | ||||
| /** The sync() call causes all modified data and directory fields | /** The sync() call causes all modified data and directory fields | ||||
| * to be written to the storage device. | * 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 a call to sync() before a file has been | |||||
| * opened or an I/O error. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool sync(); | bool sync(); | ||||
| /** Copy a file's timestamps | /** Copy a file's timestamps | ||||
| * Modify and access timestamps may be overwritten if a date time callback | * Modify and access timestamps may be overwritten if a date time callback | ||||
| * function has been set by dateTimeCallback(). | * function has been set by dateTimeCallback(). | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool timestamp(FatFile* file); | bool timestamp(FatFile* file); | ||||
| /** Set a file's timestamps in its directory entry. | /** Set a file's timestamps in its directory entry. | ||||
| * Modify and access timestamps may be overwritten if a date time callback | * Modify and access timestamps may be overwritten if a date time callback | ||||
| * function has been set by dateTimeCallback(). | * function has been set by dateTimeCallback(). | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day, | bool timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day, | ||||
| uint8_t hour, uint8_t minute, uint8_t second); | |||||
| uint8_t hour, uint8_t minute, uint8_t second); | |||||
| /** Type of file. You should use isFile() or isDir() instead of fileType() | /** Type of file. You should use isFile() or isDir() instead of fileType() | ||||
| * if possible. | * if possible. | ||||
| * | * | ||||
| * \return The file or directory type. | * \return The file or directory type. | ||||
| */ | */ | ||||
| uint8_t fileAttr() const {return m_attr;} | |||||
| uint8_t fileAttr() const { | |||||
| return m_attr; | |||||
| } | |||||
| /** Truncate a file to a specified length. The current file position | /** 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 | * will be maintained if it is less than or equal to \a length otherwise | ||||
| * it will be set to end of file. | * it will be set to end of file. | ||||
| * | * | ||||
| * \param[in] length The desired length for the 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. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool truncate(uint32_t length); | bool truncate(uint32_t length); | ||||
| /** \return FatVolume that contains this file. */ | /** \return FatVolume that contains this file. */ | ||||
| FatVolume* volume() const {return m_vol;} | |||||
| FatVolume* volume() const { | |||||
| return m_vol; | |||||
| } | |||||
| /** Write a single byte. | /** Write a single byte. | ||||
| * \param[in] b The byte to be written. | * \param[in] b The byte to be written. | ||||
| * \return +1 for success or -1 for failure. | * \return +1 for success or -1 for failure. | ||||
| */ | */ | ||||
| int write(uint8_t b) {return write(&b, 1);} | |||||
| int write(uint8_t b) { | |||||
| return write(&b, 1); | |||||
| } | |||||
| /** Write data to an open file. | /** Write data to an open file. | ||||
| * | * | ||||
| * \note Data is moved to the cache but may not be written to the | * \note Data is moved to the cache but may not be written to the | ||||
| /** Entry is for a system file. */ | /** Entry is for a system file. */ | ||||
| static const uint8_t FILE_ATTR_SYSTEM = DIR_ATT_SYSTEM; | static const uint8_t FILE_ATTR_SYSTEM = DIR_ATT_SYSTEM; | ||||
| /** Entry for normal data file */ | /** Entry for normal data file */ | ||||
| static const uint8_t FILE_ATTR_IS_OPEN = 0X08; | |||||
| static const uint8_t FILE_ATTR_FILE = 0X08; | |||||
| /** Entry is for a subdirectory */ | /** Entry is for a subdirectory */ | ||||
| static const uint8_t FILE_ATTR_SUBDIR = DIR_ATT_DIRECTORY; | static const uint8_t FILE_ATTR_SUBDIR = DIR_ATT_DIRECTORY; | ||||
| /** A FAT12 or FAT16 root directory */ | /** A FAT12 or FAT16 root directory */ | ||||
| DIR_ATT_SYSTEM | DIR_ATT_DIRECTORY; | DIR_ATT_SYSTEM | DIR_ATT_DIRECTORY; | ||||
| /** experimental don't use */ | /** experimental don't use */ | ||||
| bool openParent(FatFile* dir); | bool openParent(FatFile* dir); | ||||
| // private functions | // private functions | ||||
| bool addCluster(); | bool addCluster(); | ||||
| bool addDirCluster(); | bool addDirCluster(); | ||||
| dir_t* cacheDirEntry(uint8_t action); | dir_t* cacheDirEntry(uint8_t action); | ||||
| int8_t lsPrintNext(print_t* pr, uint8_t flags, uint8_t indent); | |||||
| static bool make83Name(const char* str, uint8_t* name, const char** ptr); | |||||
| bool mkdir(FatFile* parent, const uint8_t dname[11]); | |||||
| bool open(FatFile* dirFile, const uint8_t dname[11], uint8_t oflag); | |||||
| bool openCachedEntry(FatFile* dirFile, uint16_t cacheIndex, uint8_t oflag); | |||||
| static uint8_t lfnChecksum(uint8_t* name); | |||||
| bool lfnUniqueSfn(fname_t* fname); | |||||
| bool openCluster(FatFile* file); | |||||
| static bool parsePathName(const char* str, fname_t* fname, const char** ptr); | |||||
| bool mkdir(FatFile* parent, fname_t* fname); | |||||
| bool open(FatFile* dirFile, fname_t* fname, uint8_t oflag); | |||||
| bool openCachedEntry(FatFile* dirFile, uint16_t cacheIndex, uint8_t oflag, | |||||
| uint8_t lfnOrd); | |||||
| bool readLBN(uint32_t* lbn); | bool readLBN(uint32_t* lbn); | ||||
| dir_t* readDirCache(); | |||||
| dir_t* readDirCache(bool skipReadOk = false); | |||||
| bool setDirSize(); | bool setDirSize(); | ||||
| // bits defined in m_flags | // bits defined in m_flags | ||||
| // data time callback function | // data time callback function | ||||
| static void (*m_dateTime)(uint16_t* date, uint16_t* time); | static void (*m_dateTime)(uint16_t* date, uint16_t* time); | ||||
| // private data | // private data | ||||
| bool m_writeError; // Set when a write error occurs | |||||
| static const uint8_t WRITE_ERROR = 0X1; | |||||
| static const uint8_t READ_ERROR = 0X2; | |||||
| uint8_t m_attr; // File attributes | uint8_t m_attr; // File attributes | ||||
| uint8_t m_error; // Error bits. | |||||
| uint8_t m_flags; // See above for definition of m_flags bits | uint8_t m_flags; // See above for definition of m_flags bits | ||||
| uint8_t m_lfnOrd; | |||||
| uint16_t m_dirIndex; // index of directory entry in dir file | uint16_t m_dirIndex; // index of directory entry in dir file | ||||
| FatVolume* m_vol; // volume where file is located | FatVolume* m_vol; // volume where file is located | ||||
| uint32_t m_dirCluster; | |||||
| uint32_t m_curCluster; // cluster for current file position | uint32_t m_curCluster; // cluster for current file position | ||||
| uint32_t m_curPosition; // current file position | uint32_t m_curPosition; // current file position | ||||
| uint32_t m_dirBlock; // block for this files directory entry | uint32_t m_dirBlock; // block for this files directory entry | ||||
| uint32_t m_dirFirstCluster; // first cluster of this file's directory | |||||
| uint32_t m_fileSize; // file size in bytes | uint32_t m_fileSize; // file size in bytes | ||||
| uint32_t m_firstCluster; // first cluster of file | uint32_t m_firstCluster; // first cluster of file | ||||
| }; | }; |
| * along with the FatLib Library. If not, see | * along with the FatLib Library. If not, see | ||||
| * <http://www.gnu.org/licenses/>. | * <http://www.gnu.org/licenses/>. | ||||
| */ | */ | ||||
| #ifndef DOXYGEN_SHOULD_SKIP_THIS | |||||
| #include "FatFile.h" | #include "FatFile.h" | ||||
| bool FatFile::findSfn(uint8_t sfn[11], uint16_t* index) { | |||||
| uint16_t free = 0XFFFF; | |||||
| uint16_t idx; | |||||
| dir_t* dir; | |||||
| if (!isDir()) { | |||||
| //------------------------------------------------------------------------------ | |||||
| // | |||||
| uint8_t FatFile::lfnChecksum(uint8_t* name) { | |||||
| uint8_t sum = 0; | |||||
| for (uint8_t i = 0; i < 11; i++) { | |||||
| sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + name[i]; | |||||
| } | |||||
| return sum; | |||||
| } | |||||
| #if USE_LONG_FILE_NAMES | |||||
| //------------------------------------------------------------------------------ | |||||
| // Saves about 90 bytes of flash on 328 over tolower(). | |||||
| inline char lfnToLower(char c) { | |||||
| return 'A' <= c && c <= 'Z' ? c + 'a' - 'A' : c; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| // Daniel Bernstein University of Illinois at Chicago. | |||||
| // Original had + instead of ^ | |||||
| static uint16_t Bernstein(uint16_t hash, const char *str, size_t len) { | |||||
| for (size_t i = 0; i < len; i++) { | |||||
| // hash = hash * 33 ^ str[i]; | |||||
| hash = ((hash << 5) + hash) ^ str[i]; | |||||
| } | |||||
| return hash; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * Fetch a 16-bit long file name character. | |||||
| * | |||||
| * \param[in] ldir Pointer to long file name directory entry. | |||||
| * \param[in] i Index of character. | |||||
| * \return The 16-bit character. | |||||
| */ | |||||
| static uint16_t lfnGetChar(ldir_t *ldir, uint8_t i) { | |||||
| if (i < LDIR_NAME1_DIM) { | |||||
| return ldir->name1[i]; | |||||
| } else if (i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM)) { | |||||
| return ldir->name2[i - LDIR_NAME1_DIM]; | |||||
| } else if (i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM + LDIR_NAME2_DIM)) { | |||||
| return ldir->name3[i - LDIR_NAME1_DIM - LDIR_NAME2_DIM]; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| static bool lfnGetName(ldir_t *ldir, char* name, size_t n) { | |||||
| uint8_t i; | |||||
| size_t k = 13*((ldir->ord & 0X1F) - 1); | |||||
| for (i = 0; i < 13; i++) { | |||||
| uint16_t c = lfnGetChar(ldir, i); | |||||
| if (c == 0 || k >= n) { | |||||
| break; | |||||
| } | |||||
| name[k++] = c >= 0X7F ? '?' : c; | |||||
| } | |||||
| // Terminate with zero byte if name fits. | |||||
| if (k < n && (ldir->ord & LDIR_ORD_LAST_LONG_ENTRY)) { | |||||
| name[k] = 0; | |||||
| } | |||||
| // Truncate if name is too long. | |||||
| name[n-1] = 0; | |||||
| return true; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| inline bool lfnLegalChar(char c) { | |||||
| if (c == '/' || c == '\\' || c == '"' || c == '*' || | |||||
| c == ':' || c == '<' || c == '>' || c == '?' || c == '|') { | |||||
| return false; | |||||
| } | |||||
| return 0X1F < c && c < 0X7F; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * Store a 16-bit long file name character. | |||||
| * | |||||
| * \param[in] ldir Pointer to long file name directory entry. | |||||
| * \param[in] i Index of character. | |||||
| * \param[in] c The 16-bit character. | |||||
| */ | |||||
| static void lfnPutChar(ldir_t *ldir, uint8_t i, uint16_t c) { | |||||
| if (i < LDIR_NAME1_DIM) { | |||||
| ldir->name1[i] = c; | |||||
| } else if (i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM)) { | |||||
| ldir->name2[i - LDIR_NAME1_DIM] = c; | |||||
| } else if (i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM + LDIR_NAME2_DIM)) { | |||||
| ldir->name3[i - LDIR_NAME1_DIM - LDIR_NAME2_DIM] = c; | |||||
| } | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| static void lfnPutName(ldir_t *ldir, const char* name, size_t n) { | |||||
| size_t k = 13*((ldir->ord & 0X1F) - 1); | |||||
| for (uint8_t i = 0; i < 13; i++, k++) { | |||||
| uint16_t c = k < n ? name[k] : k == n ? 0 : 0XFFFF; | |||||
| lfnPutChar(ldir, i, c); | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| bool FatFile::getFilename(char* name, size_t size) { | |||||
| FatFile dirFile; | |||||
| ldir_t* ldir; | |||||
| if (!isOpen() || size < 13) { | |||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| rewind(); | |||||
| while (m_curPosition < m_fileSize) { | |||||
| idx = m_curPosition/32; | |||||
| if ((0XF & idx) == 0) { | |||||
| dir = readDirCache(); | |||||
| if (!dir) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| } else { | |||||
| m_curPosition += 32; | |||||
| dir++; | |||||
| if (!isLFN()) { | |||||
| return getSFN(name); | |||||
| } | |||||
| if (!dirFile.openCluster(this)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| for (uint8_t ord = 1; ord <= m_lfnOrd; ord++) { | |||||
| if (!dirFile.seekSet(32UL*(m_dirIndex - ord))) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | } | ||||
| // done if last entry | |||||
| if (dir->name[0] == DIR_NAME_FREE || dir->name[0] == DIR_NAME_DELETED) { | |||||
| if (free == 0XFFFF) free = idx; | |||||
| if (dir->name[0] == DIR_NAME_FREE) goto fail; | |||||
| } else if (DIR_IS_FILE_OR_SUBDIR(dir)) { | |||||
| if (!memcmp(sfn, dir->name, 11)) { | |||||
| *index = idx; | |||||
| return true; | |||||
| } | |||||
| ldir = reinterpret_cast<ldir_t*>(dirFile.readDirCache()); | |||||
| if (!ldir) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| if (ldir->attr != DIR_ATT_LONG_NAME) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| if (ord != (ldir->ord & 0X1F)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| if (!lfnGetName(ldir, name, size)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| if (ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) { | |||||
| return true; | |||||
| } | } | ||||
| } | } | ||||
| // Fall into fail. | |||||
| DBG_FAIL_MACRO; | |||||
| fail: | |||||
| *index = free; | |||||
| fail: | |||||
| name[0] = 0; | |||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| bool FatFile::createLfn(FatFile* dirFile, | |||||
| uint16_t bgnIndex, char* name, uint8_t oflag) { | |||||
| return false; | |||||
| bool FatFile::openCluster(FatFile* file) { | |||||
| if (file->m_dirCluster == 0) { | |||||
| return openRoot(file->m_vol); | |||||
| } | |||||
| memset(this, 0, sizeof(FatFile)); | |||||
| m_attr = FILE_ATTR_SUBDIR; | |||||
| m_flags = O_READ; | |||||
| m_vol = file->m_vol; | |||||
| m_firstCluster = file->m_dirCluster; | |||||
| return true; | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| bool FatFile::findLfn(const char* name, | |||||
| uint16_t *bgnIndex, uint16_t* endIndex) { | |||||
| bool fill; | |||||
| bool haveLong = false; | |||||
| uint8_t ndir; | |||||
| uint8_t test; | |||||
| uint16_t lfnBgn; | |||||
| uint16_t curIndex = 0XFFFF; | |||||
| uint16_t freeIndex = 0XFFFF; | |||||
| uint8_t freeCount = 0; | |||||
| bool FatFile::parsePathName(const char* path, | |||||
| fname_t* fname, const char** ptr) { | |||||
| char c; | |||||
| bool is83; | |||||
| uint8_t bit = DIR_NT_LC_BASE; | |||||
| uint8_t lc = 0; | |||||
| uint8_t uc = 0; | |||||
| uint8_t i = 0; | |||||
| uint8_t in = 7; | |||||
| size_t end; | |||||
| size_t len = 0; | |||||
| int si; | |||||
| int dot; | |||||
| // Skip leading spaces. | |||||
| while (*path == ' ') { | |||||
| path++; | |||||
| } | |||||
| fname->lfn = path; | |||||
| for (len = 0; ; len++) { | |||||
| c = path[len]; | |||||
| if (c == 0 || isDirSeparator(c)) { | |||||
| break; | |||||
| } | |||||
| if (!lfnLegalChar(c)) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| // Advance to next path component. | |||||
| for (end = len; path[end] == ' ' || isDirSeparator(path[end]); end++) {} | |||||
| *ptr = &path[end]; | |||||
| // Back over spaces and dots. | |||||
| while (len) { | |||||
| c = path[len - 1]; | |||||
| if (c != '.' && c != ' ') { | |||||
| break; | |||||
| } | |||||
| len--; | |||||
| } | |||||
| // Max length of LFN is 255. | |||||
| if (len > 255) { | |||||
| return false; | |||||
| } | |||||
| fname->len = len; | |||||
| // Blank file short name. | |||||
| for (uint8_t k = 0; k < 11; k++) { | |||||
| fname->sfn[k] = ' '; | |||||
| } | |||||
| // skip leading spaces and dots. | |||||
| for (si = 0; path[si] == '.' || path[si] == ' '; si++) {} | |||||
| // Not 8.3 if leading dot or space. | |||||
| is83 = !si; | |||||
| // find last dot. | |||||
| for (dot = len - 1; dot >= 0 && path[dot] != '.'; dot--) {} | |||||
| for (; si < len; si++) { | |||||
| c = path[si]; | |||||
| if (c == ' ' || (c == '.' && dot != si)) { | |||||
| is83 = false; | |||||
| continue; | |||||
| } | |||||
| if (!legal83Char(c) && si != dot) { | |||||
| is83 = false; | |||||
| c = '_'; | |||||
| } | |||||
| if (si == dot || i > in) { | |||||
| if (in == 10) { | |||||
| // Done - extension longer than three characters. | |||||
| is83 = false; | |||||
| break; | |||||
| } | |||||
| if (si != dot) { | |||||
| is83 = false; | |||||
| } | |||||
| // Break if no dot and base-name is longer than eight characters. | |||||
| if (si > dot) { | |||||
| break; | |||||
| } | |||||
| si = dot; | |||||
| in = 10; // Max index for full 8.3 name. | |||||
| i = 8; // Place for extension. | |||||
| bit = DIR_NT_LC_EXT; // bit for extension. | |||||
| } else { | |||||
| if ('a' <= c && c <= 'z') { | |||||
| c += 'A' - 'a'; | |||||
| lc |= bit; | |||||
| } else if ('A' <= c && c <= 'Z') { | |||||
| uc |= bit; | |||||
| } | |||||
| fname->sfn[i++] = c; | |||||
| if (i < 7) { | |||||
| fname->seqPos = i; | |||||
| } | |||||
| } | |||||
| } | |||||
| if (fname->sfn[0] == ' ') { | |||||
| return false; | |||||
| } | |||||
| if (is83) { | |||||
| fname->flags = lc & uc ? FNAME_FLAG_MIXED_CASE : lc; | |||||
| } else { | |||||
| fname->flags = FNAME_FLAG_LOST_CHARS; | |||||
| fname->sfn[fname->seqPos] = '~'; | |||||
| fname->sfn[fname->seqPos + 1] = '1'; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| bool FatFile::open(FatFile* dirFile, fname_t* fname, uint8_t oflag) { | |||||
| bool fnameFound = false; | |||||
| uint8_t lfnOrd = 0; | |||||
| uint8_t freeNeed; | uint8_t freeNeed; | ||||
| uint8_t freeFound = 0; | |||||
| uint8_t ord; | |||||
| uint8_t chksum; | |||||
| uint16_t freeIndex; | |||||
| uint16_t curIndex; | |||||
| dir_t* dir; | dir_t* dir; | ||||
| size_t len; | |||||
| bool is83; | |||||
| bool foundFree = false; | |||||
| const char* ptr; | |||||
| uint8_t name83[11]; | |||||
| if (!isDir()) { | |||||
| ldir_t* ldir; | |||||
| size_t len = fname->len; | |||||
| if (!dirFile->isDir() || isOpen()) { | |||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| is83 = make83Name(name, name83, &ptr); | |||||
| for (len = 0; name[len] !=0 && name[len] != '/'; len++) {} | |||||
| // Assume LFN. | |||||
| freeNeed = (len + 12)/13 + 1; | |||||
| rewind(); | |||||
| // Number of directory entries needed. | |||||
| freeNeed = fname->flags & FNAME_FLAG_NEED_LFN ? 1 + (len + 12)/13 : 1; | |||||
| dirFile->rewind(); | |||||
| while (1) { | while (1) { | ||||
| curIndex = m_curPosition/32; | |||||
| if (m_curPosition == m_fileSize) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // read entry into cache | |||||
| dir = readDirCache(); | |||||
| curIndex = dirFile->m_curPosition/32; | |||||
| dir = dirFile->readDirCache(true); | |||||
| if (!dir) { | if (!dir) { | ||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| if (dirFile->getError()) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // At EOF | |||||
| goto create; | |||||
| } | } | ||||
| #if 1 | |||||
| if (dir->name[0] == DIR_NAME_DELETED) { | |||||
| if (!foundFree) { | |||||
| if (freeIndex == 0XFFFF) { | |||||
| freeIndex = curIndex; | |||||
| freeCount = 0; | |||||
| } | |||||
| if (++freeCount == freeNeed) { | |||||
| foundFree = true; | |||||
| } | |||||
| if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == DIR_NAME_FREE) { | |||||
| if (freeFound == 0) { | |||||
| freeIndex = curIndex; | |||||
| } | |||||
| if (freeFound < freeNeed) { | |||||
| freeFound++; | |||||
| } | |||||
| if (dir->name[0] == DIR_NAME_FREE) { | |||||
| goto create; | |||||
| } | } | ||||
| continue; | |||||
| } else { | } else { | ||||
| if (!foundFree) freeIndex = 0XFFFF; | |||||
| } | |||||
| #endif | |||||
| if (dir->name[0] == DIR_NAME_FREE) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| if (freeFound < freeNeed) { | |||||
| freeFound = 0; | |||||
| } | |||||
| } | } | ||||
| // skip empty slot or '.' or '..' | // skip empty slot or '.' or '..' | ||||
| if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') { | if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') { | ||||
| haveLong = false; | |||||
| continue; | |||||
| } | |||||
| if (DIR_IS_LONG_NAME(dir)) { | |||||
| lfnOrd = 0; | |||||
| } else if (DIR_IS_LONG_NAME(dir)) { | |||||
| ldir_t *ldir = reinterpret_cast<ldir_t*>(dir); | ldir_t *ldir = reinterpret_cast<ldir_t*>(dir); | ||||
| if (!haveLong) { | |||||
| if ((ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) == 0) continue; | |||||
| ndir = ldir->ord & ~LDIR_ORD_LAST_LONG_ENTRY; | |||||
| if (ndir < 1 || ndir > 20) continue; | |||||
| test = ldir->chksum; | |||||
| haveLong = true; | |||||
| lfnBgn = curIndex; | |||||
| fill = true; | |||||
| } else if (ldir->ord != --ndir || test != ldir->chksum) { | |||||
| haveLong = false; | |||||
| if (!lfnOrd) { | |||||
| if ((ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) == 0) { | |||||
| continue; | |||||
| } | |||||
| lfnOrd = ord = ldir->ord & 0X1F; | |||||
| chksum = ldir->chksum; | |||||
| } else if (ldir->ord != --ord || chksum != ldir->chksum) { | |||||
| lfnOrd = 0; | |||||
| continue; | continue; | ||||
| } | } | ||||
| size_t nOff = 13*(ndir -1); | |||||
| for (int i = 12; i >= 0; i--) { | |||||
| uint16_t u = lfnChar(ldir, i); | |||||
| if (fill) { | |||||
| if (u == 0 || u == 0XFFFF) { | |||||
| continue; | |||||
| } | |||||
| if (len != (nOff + i +1)) { | |||||
| haveLong = false; | |||||
| break; | |||||
| size_t k = 13*(ord - 1); | |||||
| if (k >= len) { | |||||
| // Not found. | |||||
| lfnOrd = 0; | |||||
| continue; | |||||
| } | |||||
| for (uint8_t i = 0; i < 13; i++) { | |||||
| uint16_t u = lfnGetChar(ldir, i); | |||||
| if (k == len) { | |||||
| if (u != 0) { | |||||
| // Not found. | |||||
| lfnOrd = 0; | |||||
| } | } | ||||
| fill = false; | |||||
| break; | |||||
| } | } | ||||
| if (u > 255 || toupper(u) != toupper(name[nOff + i])) { | |||||
| haveLong = false; | |||||
| if (u > 255 || lfnToLower(u) != lfnToLower(fname->lfn[k++])) { | |||||
| // Not found. | |||||
| lfnOrd = 0; | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| } else if (DIR_IS_FILE_OR_SUBDIR(dir)) { | } else if (DIR_IS_FILE_OR_SUBDIR(dir)) { | ||||
| if (haveLong) { | |||||
| uint8_t sum = 0; | |||||
| for (uint8_t i = 0; i < 11; i++) { | |||||
| sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + dir->name[i]; | |||||
| } | |||||
| if (sum != test || ndir != 1) { | |||||
| haveLong = false; | |||||
| if (lfnOrd) { | |||||
| if (1 == ord && lfnChecksum(dir->name) == chksum) { | |||||
| goto found; | |||||
| } | } | ||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | } | ||||
| if (haveLong) goto done; | |||||
| if (is83 && !memcmp(dir->name, name83, sizeof(name83))) { | |||||
| goto done; | |||||
| if (!memcmp(dir->name, fname->sfn, sizeof(fname->sfn))) { | |||||
| if (!(fname->flags & FNAME_FLAG_LOST_CHARS)) { | |||||
| goto found; | |||||
| } | |||||
| fnameFound = true; | |||||
| } | } | ||||
| } else { | } else { | ||||
| haveLong = false; | |||||
| lfnOrd = 0; | |||||
| } | } | ||||
| } | } | ||||
| done: | |||||
| *bgnIndex = haveLong ? lfnBgn : curIndex; | |||||
| *endIndex = curIndex; | |||||
| found: | |||||
| // Don't open if create only. | |||||
| if (oflag & O_EXCL) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| goto open; | |||||
| create: | |||||
| // don't create unless O_CREAT and O_WRITE | |||||
| if (!(oflag & O_CREAT) || !(oflag & O_WRITE)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // If at EOF start in next cluster. | |||||
| if (freeFound == 0) { | |||||
| freeIndex = curIndex; | |||||
| } | |||||
| while (freeFound < freeNeed) { | |||||
| dir = dirFile->readDirCache(); | |||||
| if (!dir) { | |||||
| if (dirFile->getError()) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // EOF if no error. | |||||
| break; | |||||
| } | |||||
| freeFound++; | |||||
| } | |||||
| while (freeFound < freeNeed) { | |||||
| // Will fail if FAT16 root. | |||||
| if (!dirFile->addDirCluster()) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // Done if more than one block per cluster. Max freeNeed is 21. | |||||
| if (dirFile->m_vol->blocksPerCluster() > 1) { | |||||
| break; | |||||
| } | |||||
| freeFound += 16; | |||||
| } | |||||
| if (fnameFound) { | |||||
| if (!dirFile->lfnUniqueSfn(fname)) { | |||||
| goto fail; | |||||
| } | |||||
| } | |||||
| if (!dirFile->seekSet(32UL*freeIndex)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| lfnOrd = freeNeed - 1; | |||||
| for (uint8_t ord = lfnOrd ; ord ; ord--) { | |||||
| ldir = reinterpret_cast<ldir_t*>(dirFile->readDirCache()); | |||||
| if (!ldir) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| dirFile->m_vol->cacheDirty(); | |||||
| ldir->ord = ord == lfnOrd ? LDIR_ORD_LAST_LONG_ENTRY | ord : ord; | |||||
| ldir->attr = DIR_ATT_LONG_NAME; | |||||
| ldir->type = 0; | |||||
| ldir->chksum = lfnChecksum(fname->sfn); | |||||
| ldir->mustBeZero = 0; | |||||
| lfnPutName(ldir, fname->lfn, len); | |||||
| } | |||||
| curIndex = dirFile->m_curPosition/32; | |||||
| dir = dirFile->readDirCache(); | |||||
| if (!dir) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // initialize as empty file | |||||
| memset(dir, 0, sizeof(dir_t)); | |||||
| memcpy(dir->name, fname->sfn, 11); | |||||
| // Set base-name and extension lower case bits. | |||||
| dir->reservedNT = (DIR_NT_LC_BASE | DIR_NT_LC_EXT) & fname->flags; | |||||
| // set timestamps | |||||
| if (m_dateTime) { | |||||
| // call user date/time function | |||||
| m_dateTime(&dir->creationDate, &dir->creationTime); | |||||
| } else { | |||||
| // use default date/time | |||||
| dir->creationDate = FAT_DEFAULT_DATE; | |||||
| dir->creationTime = FAT_DEFAULT_TIME; | |||||
| } | |||||
| dir->lastAccessDate = dir->creationDate; | |||||
| dir->lastWriteDate = dir->creationDate; | |||||
| dir->lastWriteTime = dir->creationTime; | |||||
| // Force write of entry to device. | |||||
| dirFile->m_vol->cacheDirty(); | |||||
| open: | |||||
| // open entry in cache. | |||||
| if (!openCachedEntry(dirFile, curIndex, oflag, lfnOrd)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| return true; | return true; | ||||
| fail: | |||||
| *bgnIndex = foundFree ? freeIndex : curIndex; | |||||
| fail: | |||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| int FatFile::openNextLFN(FatFile* dirFile, | |||||
| char* name, size_t size, uint8_t oflag) { | |||||
| bool fill; | |||||
| bool haveLong = false; | |||||
| size_t lfnIn; | |||||
| uint8_t ndir; | |||||
| uint8_t test; | |||||
| size_t FatFile::printName(print_t* pr) { | |||||
| FatFile dirFile; | |||||
| uint16_t u; | |||||
| size_t n = 0; | |||||
| ldir_t* ldir; | |||||
| if (!isLFN()) { | |||||
| return printSFN(pr); | |||||
| } | |||||
| if (!dirFile.openCluster(this)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| for (uint8_t ord = 1; ord <= m_lfnOrd; ord++) { | |||||
| if (!dirFile.seekSet(32UL*(m_dirIndex - ord))) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| ldir = reinterpret_cast<ldir_t*>(dirFile.readDirCache()); | |||||
| if (!ldir) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| if (ldir->attr !=DIR_ATT_LONG_NAME || | |||||
| ord != (ldir->ord & 0X1F)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| for (uint8_t i = 0; i < 13; i++) { | |||||
| u = lfnGetChar(ldir, i); | |||||
| if (u == 0) { | |||||
| // End of name. | |||||
| break; | |||||
| } | |||||
| if (u > 0X7E) { | |||||
| u = '?'; | |||||
| } | |||||
| pr->write(static_cast<char>(u)); | |||||
| n++; | |||||
| } | |||||
| if (ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) { | |||||
| return n; | |||||
| } | |||||
| } | |||||
| // Fall into fail; | |||||
| DBG_FAIL_MACRO; | |||||
| fail: | |||||
| return 0; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| bool FatFile::remove() { | |||||
| bool last; | |||||
| uint8_t chksum; | |||||
| uint8_t ord; | |||||
| FatFile dirFile; | |||||
| dir_t* dir; | dir_t* dir; | ||||
| uint16_t index; | |||||
| int rtn; | |||||
| ldir_t* ldir; | |||||
| // Check for valid directory and file is not open. | |||||
| if (!dirFile->isDir() || isOpen() || size < 13) { | |||||
| // Cant' remove not open for write. | |||||
| if (!isFile() || !(m_flags & O_WRITE)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // Free any clusters. | |||||
| if (m_firstCluster && !m_vol->freeChain(m_firstCluster)) { | |||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| // Cache directory entry. | |||||
| dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); | |||||
| if (!dir) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| chksum = lfnChecksum(dir->name); | |||||
| while (1) { | |||||
| // Check for EOF. | |||||
| if (dirFile->curPosition() == dirFile->fileSize()) goto done; | |||||
| index = dirFile->curPosition()/32; | |||||
| // read entry into cache | |||||
| dir = dirFile->readDirCache(); | |||||
| if (!dir) { | |||||
| // Mark entry deleted. | |||||
| dir->name[0] = DIR_NAME_DELETED; | |||||
| // Set this file closed. | |||||
| m_attr = FILE_ATTR_CLOSED; | |||||
| // Write entry to device. | |||||
| if (!m_vol->cacheSync()) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| if (!isLFN()) { | |||||
| // Done, no LFN entries. | |||||
| return true; | |||||
| } | |||||
| if (!dirFile.openCluster(this)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| for (ord = 1; ord <= m_lfnOrd; ord++) { | |||||
| if (!dirFile.seekSet(32UL*(m_dirIndex - ord))) { | |||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| // done if last entry | |||||
| if (dir->name[0] == DIR_NAME_FREE) { | |||||
| ldir = reinterpret_cast<ldir_t*>(dirFile.readDirCache()); | |||||
| if (!ldir) { | |||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| return 0; | |||||
| goto fail; | |||||
| } | } | ||||
| // skip empty slot or '.' or '..' | |||||
| if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') { | |||||
| haveLong = false; | |||||
| continue; | |||||
| if (ldir->attr != DIR_ATT_LONG_NAME || | |||||
| ord != (ldir->ord & 0X1F) || | |||||
| chksum != ldir->chksum) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | } | ||||
| if (DIR_IS_LONG_NAME(dir)) { | |||||
| ldir_t *ldir = reinterpret_cast<ldir_t*>(dir); | |||||
| if (!haveLong) { | |||||
| if ((ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) == 0) continue; | |||||
| ndir = ldir->ord & ~LDIR_ORD_LAST_LONG_ENTRY; | |||||
| if (ndir < 1 || ndir > 20) continue; | |||||
| test = ldir->chksum; | |||||
| haveLong = true; | |||||
| rtn = 0; | |||||
| fill = true; | |||||
| lfnIn = 13*ndir; | |||||
| } else if (ldir->ord != --ndir || test != ldir->chksum) { | |||||
| haveLong = false; | |||||
| continue; | |||||
| last = ldir->ord & LDIR_ORD_LAST_LONG_ENTRY; | |||||
| ldir->ord = DIR_NAME_DELETED; | |||||
| m_vol->cacheDirty(); | |||||
| if (last) { | |||||
| if (!m_vol->cacheSync()) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | } | ||||
| for (int i = 12; i >= 0; i--) { | |||||
| uint16_t u = lfnChar(ldir, i); | |||||
| if (fill) { | |||||
| if (rtn == 0 && u != 0 && u != 0XFFFF) rtn = lfnIn; | |||||
| if (u == 0 || u == 0XFFFF || lfnIn >= size) { | |||||
| lfnIn--; | |||||
| continue; | |||||
| } | |||||
| name[lfnIn] = 0; | |||||
| fill = false; | |||||
| } | |||||
| if (lfnIn == 0 || u > 255) { | |||||
| haveLong = false; | |||||
| break; | |||||
| } | |||||
| name[--lfnIn] = u; | |||||
| return true; | |||||
| } | |||||
| } | |||||
| // Fall into fail. | |||||
| DBG_FAIL_MACRO; | |||||
| fail: | |||||
| return false; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| bool FatFile::lfnUniqueSfn(fname_t* fname) { | |||||
| const uint8_t FIRST_HASH_SEQ = 2; // min value is 2 | |||||
| uint8_t pos = fname->seqPos;; | |||||
| dir_t *dir; | |||||
| uint16_t hex; | |||||
| DBG_HALT_IF(!(fname->flags & FNAME_FLAG_LOST_CHARS)); | |||||
| DBG_HALT_IF(fname->sfn[pos] != '~' && fname->sfn[pos + 1] != '1'); | |||||
| for (uint8_t seq = 2; seq < 100; seq++) { | |||||
| if (seq < FIRST_HASH_SEQ) { | |||||
| fname->sfn[pos + 1] = '0' + seq; | |||||
| } else { | |||||
| DBG_PRINT_IF(seq > FIRST_HASH_SEQ); | |||||
| hex = Bernstein(seq + fname->len, fname->lfn, fname->len); | |||||
| if (pos > 3) { | |||||
| // Make space in name for ~HHHH. | |||||
| pos = 3; | |||||
| } | } | ||||
| } else if (DIR_IS_FILE_OR_SUBDIR(dir)) { | |||||
| if (haveLong) { | |||||
| uint8_t sum = 0; | |||||
| for (uint8_t i = 0; i < 11; i++) { | |||||
| sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + dir->name[i]; | |||||
| } | |||||
| if (sum != test || ndir != 1) { | |||||
| haveLong = false; | |||||
| } | |||||
| for (uint8_t i = pos + 4 ; i > pos; i--) { | |||||
| uint8_t h = hex & 0XF; | |||||
| fname->sfn[i] = h < 10 ? h + '0' : h + 'A' - 10; | |||||
| hex >>= 4; | |||||
| } | } | ||||
| if (!haveLong) { | |||||
| rtn = dirName(dir, name); | |||||
| if (dir->reservedNT) { | |||||
| uint8_t lowerTest = 0X08; | |||||
| for (char *ptr = name; *ptr; ptr++) { | |||||
| if (*ptr == '.') { | |||||
| lowerTest = 0X10; | |||||
| continue; | |||||
| } | |||||
| if (dir->reservedNT & lowerTest) { | |||||
| *ptr = tolower(*ptr); | |||||
| } | |||||
| } | |||||
| } | |||||
| fname->sfn[pos] = '~'; | |||||
| rewind(); | |||||
| while (1) { | |||||
| dir = readDirCache(true); | |||||
| if (!dir) { | |||||
| if (!getError()) { | |||||
| // At EOF and name not found if no error. | |||||
| goto done; | |||||
| } | } | ||||
| } | |||||
| if (!openCachedEntry(dirFile, index, oflag)) { | |||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| return rtn; | |||||
| } else { | |||||
| haveLong = false; | |||||
| if (dir->name[0] == DIR_NAME_FREE) { | |||||
| goto done; | |||||
| } | |||||
| if (DIR_IS_FILE_OR_SUBDIR(dir) && !memcmp(fname->sfn, dir->name, 11)) { | |||||
| // Name found - try another. | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| // fall inti fail - too many tries. | |||||
| DBG_FAIL_MACRO; | |||||
| done: | |||||
| return 0; | |||||
| fail: | |||||
| return false; | |||||
| fail: | |||||
| return -1; | |||||
| done: | |||||
| return true; | |||||
| } | } | ||||
| #endif // DOXYGEN_SHOULD_SKIP_THIS | |||||
| #endif // #if USE_LONG_FILE_NAMES |
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void FatFile::ls(print_t* pr, uint8_t flags, uint8_t indent) { | void FatFile::ls(print_t* pr, uint8_t flags, uint8_t indent) { | ||||
| if (!isDir()) return; | |||||
| FatFile file; | |||||
| rewind(); | rewind(); | ||||
| int8_t status; | |||||
| while ((status = lsPrintNext(pr, flags, indent))) { | |||||
| if (status > 1 && (flags & LS_R)) { | |||||
| uint16_t index = curPosition()/32 - 1; | |||||
| FatFile s; | |||||
| if (s.open(this, index, O_READ)) s.ls(pr, flags, indent + 2); | |||||
| seekSet(32 * (index + 1)); | |||||
| while (file.openNext(this, O_READ)) { | |||||
| // indent for dir level | |||||
| if (!file.isHidden() && !(flags & LS_A)) { | |||||
| for (uint8_t i = 0; i < indent; i++) { | |||||
| pr->write(' '); | |||||
| } | |||||
| if (flags & LS_DATE) { | |||||
| file.printModifyDateTime(pr); | |||||
| pr->write(' '); | |||||
| } | |||||
| if (flags & LS_SIZE) { | |||||
| file.printFileSize(pr); | |||||
| pr->write(' '); | |||||
| } | |||||
| file.printName(pr); | |||||
| if (file.isDir()) { | |||||
| pr->write('/'); | |||||
| } | |||||
| pr->write('\r'); | |||||
| pr->write('\n'); | |||||
| if ((flags & LS_R) && file.isDir()) { | |||||
| file.ls(pr, flags, indent + 2); | |||||
| } | |||||
| } | } | ||||
| file.close(); | |||||
| } | } | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // saves 32 bytes on stack for ls recursion | |||||
| // return 0 - EOF, 1 - normal file, or 2 - directory | |||||
| int8_t FatFile::lsPrintNext(print_t* pr, uint8_t flags, uint8_t indent) { | |||||
| dir_t dir; | |||||
| uint8_t w = 0; | |||||
| while (1) { | |||||
| if (read(&dir, sizeof(dir)) != sizeof(dir)) return 0; | |||||
| if (dir.name[0] == DIR_NAME_FREE) return 0; | |||||
| // skip deleted entry and entries for . and .. | |||||
| if (dir.name[0] != DIR_NAME_DELETED && dir.name[0] != '.' | |||||
| && DIR_IS_FILE_OR_SUBDIR(&dir)) break; | |||||
| } | |||||
| // indent for dir level | |||||
| for (uint8_t i = 0; i < indent; i++) pr->write(' '); | |||||
| // print name | |||||
| for (uint8_t i = 0; i < 11; i++) { | |||||
| if (dir.name[i] == ' ')continue; | |||||
| if (i == 8) { | |||||
| pr->write('.'); | |||||
| w++; | |||||
| } | |||||
| pr->write(dir.name[i]); | |||||
| w++; | |||||
| } | |||||
| if (DIR_IS_SUBDIR(&dir)) { | |||||
| pr->write('/'); | |||||
| w++; | |||||
| } | |||||
| if (flags & (LS_DATE | LS_SIZE)) { | |||||
| while (w++ < 14) pr->write(' '); | |||||
| } | |||||
| // print modify date/time if requested | |||||
| if (flags & LS_DATE) { | |||||
| pr->write(' '); | |||||
| printFatDate(pr, dir.lastWriteDate); | |||||
| pr->write(' '); | |||||
| printFatTime(pr, dir.lastWriteTime); | |||||
| } | |||||
| // print size if requested | |||||
| if (!DIR_IS_SUBDIR(&dir) && (flags & LS_SIZE)) { | |||||
| pr->write(' '); | |||||
| printU32(pr, dir.fileSize); | |||||
| } | |||||
| pr->write('\r'); | |||||
| pr->write('\n'); | |||||
| return DIR_IS_FILE(&dir) ? 1 : 2; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| bool FatFile::printCreateDateTime(print_t* pr) { | bool FatFile::printCreateDateTime(print_t* pr) { | ||||
| dir_t dir; | dir_t dir; | ||||
| if (!dirEntry(&dir)) { | if (!dirEntry(&dir)) { | ||||
| printFatTime(pr, dir.creationTime); | printFatTime(pr, dir.creationTime); | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| printFatTime(pr, dir.lastWriteTime); | printFatTime(pr, dir.lastWriteTime); | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | |||||
| size_t FatFile::printName(print_t* pr) { | |||||
| char name[13]; | |||||
| if (!getFilename(name)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| return pr->write(name); | |||||
| fail: | |||||
| return 0; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| size_t FatFile::printFileSize(print_t* pr) { | size_t FatFile::printFileSize(print_t* pr) { | ||||
| char buf[11]; | char buf[11]; | ||||
| char *ptr = buf + sizeof(buf); | char *ptr = buf + sizeof(buf); | ||||
| *--ptr = 0; | *--ptr = 0; | ||||
| ptr = fmtDec(fileSize(), ptr); | ptr = fmtDec(fileSize(), ptr); | ||||
| while (ptr > buf) *--ptr = ' '; | |||||
| while (ptr > buf) { | |||||
| *--ptr = ' '; | |||||
| } | |||||
| return pr->write(buf); | return pr->write(buf); | ||||
| } | } |
| /* FatLib Library | |||||
| * Copyright (C) 2012 by William Greiman | |||||
| * | |||||
| * This file is part of the FatLib Library | |||||
| * | |||||
| * 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 FatLib Library. If not, see | |||||
| * <http://www.gnu.org/licenses/>. | |||||
| */ | |||||
| #include "FatFile.h" | |||||
| #include "FatFileSystem.h" | |||||
| //------------------------------------------------------------------------------ | |||||
| bool FatFile::getSFN(char* name) { | |||||
| dir_t* dir; | |||||
| if (!isOpen()) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| if (isRoot()) { | |||||
| name[0] = '/'; | |||||
| name[1] = '\0'; | |||||
| return true; | |||||
| } | |||||
| // cache entry | |||||
| dir = cacheDirEntry(FatCache::CACHE_FOR_READ); | |||||
| if (!dir) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // format name | |||||
| dirName(dir, name); | |||||
| return true; | |||||
| fail: | |||||
| return false; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| size_t FatFile::printSFN(print_t* pr) { | |||||
| char name[13]; | |||||
| if (!getSFN(name)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| return pr->write(name); | |||||
| fail: | |||||
| return 0; | |||||
| } | |||||
| #if !USE_LONG_FILE_NAMES | |||||
| //------------------------------------------------------------------------------ | |||||
| bool FatFile::getFilename(char* name, size_t size) { | |||||
| return size < 13 ? 0 : getSFN(name); | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| // format directory name field from a 8.3 name string | |||||
| bool FatFile::parsePathName(const char* path, fname_t* fname, | |||||
| const char** ptr) { | |||||
| uint8_t uc = 0; | |||||
| uint8_t lc = 0; | |||||
| uint8_t bit = FNAME_FLAG_LC_BASE; | |||||
| // blank fill name and extension | |||||
| for (uint8_t i = 0; i < 11; i++) { | |||||
| fname->sfn[i] = ' '; | |||||
| } | |||||
| for (uint8_t i = 0, n = 7;; path++) { | |||||
| uint8_t c = *path; | |||||
| if (c == 0 || isDirSeparator(c)) { | |||||
| // Done. | |||||
| break; | |||||
| } | |||||
| if (c == '.' && n == 7) { | |||||
| n = 10; // max index for full 8.3 name | |||||
| i = 8; // place for extension | |||||
| // bit for extension. | |||||
| bit = FNAME_FLAG_LC_EXT; | |||||
| } else { | |||||
| if (!legal83Char(c) || i > n) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| if ('a' <= c && c <= 'z') { | |||||
| c += 'A' - 'a'; | |||||
| lc |= bit; | |||||
| } else if ('A' <= c && c <= 'Z') { | |||||
| uc |= bit; | |||||
| } | |||||
| fname->sfn[i++] = c; | |||||
| } | |||||
| } | |||||
| // must have a file name, extension is optional | |||||
| if (fname->sfn[0] == ' ') { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // Set base-name and extension bits. | |||||
| fname->flags = lc & uc ? 0 : lc; | |||||
| while (isDirSeparator(*path)) { | |||||
| path++; | |||||
| } | |||||
| *ptr = path; | |||||
| return true; | |||||
| fail: | |||||
| return false; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| // open with filename in fname | |||||
| #define SFN_OPEN_USES_CHKSUM 0 | |||||
| bool FatFile::open(FatFile* dirFile, fname_t* fname, uint8_t oflag) { | |||||
| bool emptyFound = false; | |||||
| #if SFN_OPEN_USES_CHKSUM | |||||
| uint8_t chksum; | |||||
| #endif | |||||
| uint8_t lfnOrd = 0; | |||||
| uint16_t emptyIndex; | |||||
| uint16_t index = 0; | |||||
| dir_t* dir; | |||||
| ldir_t* ldir; | |||||
| dirFile->rewind(); | |||||
| while (1) { | |||||
| if (!emptyFound) { | |||||
| emptyIndex = index; | |||||
| } | |||||
| dir = dirFile->readDirCache(true); | |||||
| if (!dir) { | |||||
| if (dirFile->getError()) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // At EOF if no error. | |||||
| break; | |||||
| } | |||||
| if (dir->name[0] == DIR_NAME_FREE) { | |||||
| emptyFound = true; | |||||
| break; | |||||
| } | |||||
| if (dir->name[0] == DIR_NAME_DELETED) { | |||||
| lfnOrd = 0; | |||||
| emptyFound = true; | |||||
| } else if (DIR_IS_FILE_OR_SUBDIR(dir)) { | |||||
| if (!memcmp(fname->sfn, dir->name, 11)) { | |||||
| // don't open existing file if O_EXCL | |||||
| if (oflag & O_EXCL) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| #if SFN_OPEN_USES_CHKSUM | |||||
| if (lfnOrd && chksum != lfnChecksum(dir->name)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| #endif // SFN_OPEN_USES_CHKSUM | |||||
| if (!openCachedEntry(dirFile, index, oflag, lfnOrd)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| return true; | |||||
| } else { | |||||
| lfnOrd = 0; | |||||
| } | |||||
| } else if (DIR_IS_LONG_NAME(dir)) { | |||||
| ldir = (ldir_t*)dir; | |||||
| if (ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) { | |||||
| lfnOrd = ldir->ord & 0X1F; | |||||
| #if SFN_OPEN_USES_CHKSUM | |||||
| chksum = ldir->chksum; | |||||
| #endif // SFN_OPEN_USES_CHKSUM | |||||
| } | |||||
| } else { | |||||
| lfnOrd = 0; | |||||
| } | |||||
| index++; | |||||
| } | |||||
| // don't create unless O_CREAT and O_WRITE | |||||
| if (!(oflag & O_CREAT) || !(oflag & O_WRITE)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| if (emptyFound) { | |||||
| index = emptyIndex; | |||||
| } else { | |||||
| if (!dirFile->addDirCluster()) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| } | |||||
| if (!dirFile->seekSet(32UL*index)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| dir = dirFile->readDirCache(); | |||||
| if (!dir) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // initialize as empty file | |||||
| memset(dir, 0, sizeof(dir_t)); | |||||
| memcpy(dir->name, fname->sfn, 11); | |||||
| // Set base-name and extension lower case bits. | |||||
| dir->reservedNT = (DIR_NT_LC_BASE | DIR_NT_LC_EXT) & fname->flags; | |||||
| // set timestamps | |||||
| if (m_dateTime) { | |||||
| // call user date/time function | |||||
| m_dateTime(&dir->creationDate, &dir->creationTime); | |||||
| } else { | |||||
| // use default date/time | |||||
| dir->creationDate = FAT_DEFAULT_DATE; | |||||
| dir->creationTime = FAT_DEFAULT_TIME; | |||||
| } | |||||
| dir->lastAccessDate = dir->creationDate; | |||||
| dir->lastWriteDate = dir->creationDate; | |||||
| dir->lastWriteTime = dir->creationTime; | |||||
| // Force write of entry to device. | |||||
| dirFile->m_vol->cacheDirty(); | |||||
| // open entry in cache. | |||||
| return openCachedEntry(dirFile, index, oflag, 0); | |||||
| fail: | |||||
| return false; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| size_t FatFile::printName(print_t* pr) { | |||||
| return printSFN(pr); | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| bool FatFile::remove() { | |||||
| dir_t* dir; | |||||
| // Can't remove if LFN or not open for write. | |||||
| if (!isFile() || isLFN() || !(m_flags & O_WRITE)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // Free any clusters. | |||||
| if (m_firstCluster && !m_vol->freeChain(m_firstCluster)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // Cache directory entry. | |||||
| dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); | |||||
| if (!dir) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| // Mark entry deleted. | |||||
| dir->name[0] = DIR_NAME_DELETED; | |||||
| // Set this file closed. | |||||
| m_attr = FILE_ATTR_CLOSED; | |||||
| // Write entry to device. | |||||
| return m_vol->cacheSync(); | |||||
| fail: | |||||
| return false; | |||||
| } | |||||
| #endif // !USE_LONG_FILE_NAMES |
| */ | */ | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** FatFileSystem version YYYYMMDD */ | /** FatFileSystem version YYYYMMDD */ | ||||
| #define FAT_LIB_VERSION 20141115 | |||||
| #define FAT_LIB_VERSION 20141201 | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** | /** | ||||
| * \class FatFileSystem | * \class FatFileSystem | ||||
| /** | /** | ||||
| * Initialize an FatFileSystem object. | * Initialize an FatFileSystem object. | ||||
| * \param[in] d Volume Working Directory. | * \param[in] d Volume Working Directory. | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool begin(FatFile* d) { | bool begin(FatFile* d) { | ||||
| m_vwd = d; | m_vwd = d; | ||||
| * \param[in] set_cwd Set the current working directory to this volume's | * \param[in] set_cwd Set the current working directory to this volume's | ||||
| * working directory if true. | * working directory if true. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool chdir(bool set_cwd = false) { | bool chdir(bool set_cwd = false) { | ||||
| vwd()->close(); | vwd()->close(); | ||||
| * \param[in] set_cwd Set the current working directory to this volume's | * \param[in] set_cwd Set the current working directory to this volume's | ||||
| * working directory if true. | * working directory if true. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| bool chdir(const char *path, bool set_cwd = false) { | bool chdir(const char *path, bool set_cwd = false) { | ||||
| FatFile dir; | FatFile dir; | ||||
| if (path[0] == '/' && path[1] == '\0') return chdir(set_cwd); | |||||
| if (!dir.open(vwd(), path, O_READ)) goto fail; | |||||
| if (!dir.isDir()) goto fail; | |||||
| if (path[0] == '/' && path[1] == '\0') { | |||||
| return chdir(set_cwd); | |||||
| } | |||||
| if (!dir.open(vwd(), path, O_READ)) { | |||||
| goto fail; | |||||
| } | |||||
| if (!dir.isDir()) { | |||||
| goto fail; | |||||
| } | |||||
| *m_vwd = dir; | *m_vwd = dir; | ||||
| if (set_cwd) FatFile::setCwd(vwd()); | |||||
| if (set_cwd) { | |||||
| FatFile::setCwd(vwd()); | |||||
| } | |||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| return false; | return false; | ||||
| } | } | ||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| * | * | ||||
| * \param[in] pFlag Create missing parent directories if true. | * \param[in] pFlag Create missing parent directories if true. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool mkdir(const char* path, bool pFlag = true) { | bool mkdir(const char* path, bool pFlag = true) { | ||||
| FatFile sub; | FatFile sub; | ||||
| * | * | ||||
| * \param[in] path A path with a valid 8.3 DOS name for the file. | * \param[in] path A path with a valid 8.3 DOS name for the file. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool remove(const char* path) { | bool remove(const char* path) { | ||||
| return FatFile::remove(vwd(), path); | return FatFile::remove(vwd(), path); | ||||
| * moved and file system corruption could occur if the file is accessed by | * moved and file system corruption could occur if the file is accessed by | ||||
| * a file object that was opened before the rename() call. | * a file object that was opened before the rename() call. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool rename(const char *oldPath, const char *newPath) { | bool rename(const char *oldPath, const char *newPath) { | ||||
| FatFile file; | FatFile file; | ||||
| if (!file.open(vwd(), oldPath, O_READ)) return false; | |||||
| if (!file.open(vwd(), oldPath, O_READ)) { | |||||
| return false; | |||||
| } | |||||
| return file.rename(vwd(), newPath); | return file.rename(vwd(), newPath); | ||||
| } | } | ||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| * | * | ||||
| * The subdirectory file will be removed only if it is empty. | * The subdirectory file will be removed only if it is empty. | ||||
| * | * | ||||
| * \return The value one, true, is returned for success and | |||||
| * the value zero, false, is returned for failure. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool rmdir(const char* path) { | bool rmdir(const char* path) { | ||||
| FatFile sub; | FatFile sub; | ||||
| if (!sub.open(vwd(), path, O_READ)) return false; | |||||
| if (!sub.open(vwd(), path, O_READ)) { | |||||
| return false; | |||||
| } | |||||
| return sub.rmdir(); | return sub.rmdir(); | ||||
| } | } | ||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| * \param[in] path A path with a valid 8.3 DOS name for the file. | * \param[in] path A path with a valid 8.3 DOS name for the file. | ||||
| * \param[in] length The desired length for the 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. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool truncate(const char* path, uint32_t length) { | bool truncate(const char* path, uint32_t length) { | ||||
| FatFile file; | FatFile file; | ||||
| if (!file.open(vwd(), path, O_WRITE)) return false; | |||||
| if (!file.open(vwd(), path, O_WRITE)) { | |||||
| return false; | |||||
| } | |||||
| return file.truncate(length); | return file.truncate(length); | ||||
| } | } | ||||
| /** \return a pointer to the FatVolume object. */ | /** \return a pointer to the FatVolume object. */ | ||||
| FatVolume* vol() {return this;} | |||||
| FatVolume* vol() { | |||||
| return this; | |||||
| } | |||||
| /** \return a pointer to the volume working directory. */ | /** \return a pointer to the volume working directory. */ | ||||
| FatFile* vwd() {return m_vwd;} | |||||
| FatFile* vwd() { | |||||
| return m_vwd; | |||||
| } | |||||
| /** Wipe all data from the volume. You must reinitialize the volume before | |||||
| * accessing it again. | |||||
| * \param[in] pr print stream for status dots. | |||||
| * \return true for success else false. | |||||
| */ | |||||
| bool wipe(print_t* pr = 0) { | |||||
| m_vwd->close(); | |||||
| return FatVolume::wipe(pr); | |||||
| } | |||||
| private: | private: | ||||
| FatFile* m_vwd; | FatFile* m_vwd; |
| */ | */ | ||||
| #ifndef FatLibConfig_h | #ifndef FatLibConfig_h | ||||
| #define FatLibConfig_h | #define FatLibConfig_h | ||||
| /** Use SdFatConfig.h if nonzero */ | /** Use SdFatConfig.h if nonzero */ | ||||
| #define USE_SDFAT_CONFIG 1 | #define USE_SDFAT_CONFIG 1 | ||||
| #if USE_SDFAT_CONFIG | #if USE_SDFAT_CONFIG | ||||
| #include "../SdFatConfig.h" | #include "../SdFatConfig.h" | ||||
| #if !defined(USE_LONG_FILE_NAMES) || !defined(USE_SEPARATE_FAT_CACHE) || \ | |||||
| !defined(USE_MULTI_BLOCK_IO) || !defined(DESTRUCTOR_CLOSES_FILE) || \ | |||||
| !defined(ENDL_CALLS_FLUSH) || !defined(FAT12_SUPPORT) | |||||
| #error Undefined congiguration symbols. | |||||
| #endif // Configuration symbols | |||||
| #else // USE_SDFAT_CONFIG | #else // USE_SDFAT_CONFIG | ||||
| #include <stdint.h> | #include <stdint.h> | ||||
| #ifdef __AVR__ | #ifdef __AVR__ | ||||
| #include <avr/io.h> | #include <avr/io.h> | ||||
| #endif // __AVR__ | #endif // __AVR__ | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** | |||||
| * Set USE_LONG_FILE_NAMES nonzero to use long file names (LFN). | |||||
| * Long File Name are limited to a maximum length of 255 characters. | |||||
| * | |||||
| * This implementation allows 7-bit characters in the range | |||||
| * 0X20 to 0X7E. The following characters are not allowed: | |||||
| * | |||||
| * < (less than) | |||||
| * > (greater than) | |||||
| * : (colon) | |||||
| * " (double quote) | |||||
| * / (forward slash) | |||||
| * \ (backslash) | |||||
| * | (vertical bar or pipe) | |||||
| * ? (question mark) | |||||
| * * (asterisk) | |||||
| * | |||||
| */ | |||||
| #define USE_LONG_FILE_NAMES 1 | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | /** | ||||
| * Set USE_SEPARATE_FAT_CACHE non-zero to use a second 512 byte cache | * Set USE_SEPARATE_FAT_CACHE non-zero to use a second 512 byte cache | ||||
| * for FAT table entries. Improves performance for large writes that | * for FAT table entries. Improves performance for large writes that | ||||
| * are not a multiple of 512 bytes. | * are not a multiple of 512 bytes. | ||||
| */ | */ | ||||
| #ifdef __arm__ | #ifdef __arm__ | ||||
| #define USE_SEPARATE_FAT_CACHE 1 | #define USE_SEPARATE_FAT_CACHE 1 | ||||
| #else // __arm__ | #else // __arm__ |
| * The MBR partition table has four entries. | * The MBR partition table has four entries. | ||||
| */ | */ | ||||
| struct partitionTable { | struct partitionTable { | ||||
| /** | |||||
| * Boot Indicator . Indicates whether the volume is the active | |||||
| * partition. Legal values include: 0X00. Do not use for booting. | |||||
| * 0X80 Active partition. | |||||
| */ | |||||
| /** | |||||
| * 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; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | unsigned beginSector : 6; | ||||
| /** High bits cylinder for first block in partition. */ | |||||
| /** High bits cylinder for first block in partition. */ | |||||
| unsigned beginCylinderHigh : 2; | unsigned beginCylinderHigh : 2; | ||||
| /** | |||||
| * Combine beginCylinderLow with beginCylinderHigh. Legal values | |||||
| * are 0-1023. Only used in old PC BIOS. | |||||
| */ | |||||
| /** | |||||
| * Combine beginCylinderLow with beginCylinderHigh. Legal values | |||||
| * are 0-1023. Only used in old PC BIOS. | |||||
| */ | |||||
| uint8_t beginCylinderLow; | uint8_t beginCylinderLow; | ||||
| /** | |||||
| * Partition type. See defines that begin with PART_TYPE_ for | |||||
| * some Microsoft partition types. | |||||
| */ | |||||
| /** | |||||
| * Partition type. See defines that begin with PART_TYPE_ for | |||||
| * some Microsoft partition types. | |||||
| */ | |||||
| uint8_t type; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | unsigned endSector : 6; | ||||
| /** High bits of end cylinder */ | |||||
| /** High bits of end cylinder */ | |||||
| unsigned endCylinderHigh : 2; | unsigned endCylinderHigh : 2; | ||||
| /** | |||||
| * Combine endCylinderLow with endCylinderHigh. Legal values | |||||
| * are 0-1023. Only used in old PC BIOS. | |||||
| */ | |||||
| /** | |||||
| * Combine endCylinderLow with endCylinderHigh. Legal values | |||||
| * are 0-1023. Only used in old PC BIOS. | |||||
| */ | |||||
| uint8_t endCylinderLow; | uint8_t endCylinderLow; | ||||
| /** Logical block address of the first block in the partition. */ | |||||
| /** Logical block address of the first block in the partition. */ | |||||
| uint32_t firstSector; | uint32_t firstSector; | ||||
| /** Length of the partition, in blocks. */ | |||||
| /** Length of the partition, in blocks. */ | |||||
| uint32_t totalSectors; | uint32_t totalSectors; | ||||
| }__attribute__((packed)); | |||||
| } __attribute__((packed)); | |||||
| /** Type name for partitionTable */ | /** Type name for partitionTable */ | ||||
| typedef struct partitionTable part_t; | typedef struct partitionTable part_t; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| * The first block of a storage device that is formatted with a MBR. | * The first block of a storage device that is formatted with a MBR. | ||||
| */ | */ | ||||
| struct masterBootRecord { | struct masterBootRecord { | ||||
| /** Code Area for master boot program. */ | |||||
| /** Code Area for master boot program. */ | |||||
| uint8_t codeArea[440]; | uint8_t codeArea[440]; | ||||
| /** Optional Windows NT disk signature. May contain boot code. */ | |||||
| /** Optional Windows NT disk signature. May contain boot code. */ | |||||
| uint32_t diskSignature; | uint32_t diskSignature; | ||||
| /** Usually zero but may be more boot code. */ | |||||
| /** Usually zero but may be more boot code. */ | |||||
| uint16_t usuallyZero; | uint16_t usuallyZero; | ||||
| /** Partition tables. */ | |||||
| /** Partition tables. */ | |||||
| part_t part[4]; | part_t part[4]; | ||||
| /** First MBR signature byte. Must be 0X55 */ | |||||
| /** First MBR signature byte. Must be 0X55 */ | |||||
| uint8_t mbrSig0; | uint8_t mbrSig0; | ||||
| /** Second MBR signature byte. Must be 0XAA */ | |||||
| /** Second MBR signature byte. Must be 0XAA */ | |||||
| uint8_t mbrSig1; | uint8_t mbrSig1; | ||||
| }__attribute__((packed)); | |||||
| } __attribute__((packed)); | |||||
| /** Type name for masterBootRecord */ | /** Type name for masterBootRecord */ | ||||
| typedef struct masterBootRecord mbr_t; | typedef struct masterBootRecord mbr_t; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| * | * | ||||
| */ | */ | ||||
| struct fat_boot { | struct fat_boot { | ||||
| /** | |||||
| * The first three bytes of the boot sector must be valid, | |||||
| * executable x 86-based CPU instructions. This includes a | |||||
| * jump instruction that skips the next non-executable bytes. | |||||
| */ | |||||
| /** | |||||
| * The first three bytes of the boot sector must be valid, | |||||
| * executable x 86-based CPU instructions. This includes a | |||||
| * jump instruction that skips the next non-executable bytes. | |||||
| */ | |||||
| uint8_t jump[3]; | uint8_t jump[3]; | ||||
| /** | |||||
| * This is typically a string of characters that identifies | |||||
| * the operating system that formatted the volume. | |||||
| */ | |||||
| /** | |||||
| * This is typically a string of characters that identifies | |||||
| * the operating system that formatted the volume. | |||||
| */ | |||||
| char oemId[8]; | char oemId[8]; | ||||
| /** | |||||
| * The size of a hardware sector. Valid decimal values for this | |||||
| * field are 512, 1024, 2048, and 4096. For most disks used in | |||||
| * the United States, the value of this field is 512. | |||||
| */ | |||||
| /** | |||||
| * The size of a hardware sector. Valid decimal values for this | |||||
| * field are 512, 1024, 2048, and 4096. For most disks used in | |||||
| * the United States, the value of this field is 512. | |||||
| */ | |||||
| uint16_t bytesPerSector; | 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. 128 should be avoided. | |||||
| */ | |||||
| /** | |||||
| * 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. 128 should be avoided. | |||||
| */ | |||||
| uint8_t sectorsPerCluster; | uint8_t sectorsPerCluster; | ||||
| /** | |||||
| * The number of sectors preceding the start of the first FAT, | |||||
| * including the boot sector. The value of this field is always 1. | |||||
| */ | |||||
| /** | |||||
| * The number of sectors preceding the start of the first FAT, | |||||
| * including the boot sector. The value of this field is always 1. | |||||
| */ | |||||
| uint16_t reservedSectorCount; | uint16_t reservedSectorCount; | ||||
| /** | |||||
| * The number of copies of the FAT on the volume. | |||||
| * The value of this field is always 2. | |||||
| */ | |||||
| /** | |||||
| * The number of copies of the FAT on the volume. | |||||
| * The value of this field is always 2. | |||||
| */ | |||||
| uint8_t fatCount; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | 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). | |||||
| */ | |||||
| /** | |||||
| * 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; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | uint16_t sectorsPerFat16; | ||||
| /** Sectors per track for interrupt 0x13. Not used otherwise. */ | |||||
| /** Sectors per track for interrupt 0x13. Not used otherwise. */ | |||||
| uint16_t sectorsPerTrack; | uint16_t sectorsPerTrack; | ||||
| /** Number of heads for interrupt 0x13. Not used otherwise. */ | |||||
| /** Number of heads for interrupt 0x13. Not used otherwise. */ | |||||
| uint16_t headCount; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | uint32_t totalSectors32; | ||||
| /** | |||||
| * Related to the BIOS physical drive number. Floppy drives are | |||||
| * identified as 0x00 and physical hard disks are identified as | |||||
| * 0x80, regardless of the number of physical disk drives. | |||||
| * Typically, this value is set prior to issuing an INT 13h BIOS | |||||
| * call to specify the device to access. The value is only | |||||
| * relevant if the device is a boot device. | |||||
| */ | |||||
| /** | |||||
| * Related to the BIOS physical drive number. Floppy drives are | |||||
| * identified as 0x00 and physical hard disks are identified as | |||||
| * 0x80, regardless of the number of physical disk drives. | |||||
| * Typically, this value is set prior to issuing an INT 13h BIOS | |||||
| * call to specify the device to access. The value is only | |||||
| * relevant if the device is a boot device. | |||||
| */ | |||||
| uint8_t driveNumber; | uint8_t driveNumber; | ||||
| /** used by Windows NT - should be zero for FAT */ | |||||
| /** used by Windows NT - should be zero for FAT */ | |||||
| uint8_t reserved1; | uint8_t reserved1; | ||||
| /** 0X29 if next three fields are valid */ | |||||
| /** 0X29 if next three fields are valid */ | |||||
| uint8_t bootSignature; | uint8_t bootSignature; | ||||
| /** | |||||
| * A random serial number created when formatting a disk, | |||||
| * which helps to distinguish between disks. | |||||
| * Usually generated by combining date and time. | |||||
| */ | |||||
| /** | |||||
| * A random serial number created when formatting a disk, | |||||
| * which helps to distinguish between disks. | |||||
| * Usually generated by combining date and time. | |||||
| */ | |||||
| uint32_t volumeSerialNumber; | uint32_t volumeSerialNumber; | ||||
| /** | |||||
| * A field once used to store the volume label. The volume label | |||||
| * is now stored as a special file in the root directory. | |||||
| */ | |||||
| /** | |||||
| * A field once used to store the volume label. The volume label | |||||
| * is now stored as a special file in the root directory. | |||||
| */ | |||||
| char volumeLabel[11]; | char volumeLabel[11]; | ||||
| /** | |||||
| * A field with a value of either FAT, FAT12 or FAT16, | |||||
| * depending on the disk format. | |||||
| */ | |||||
| /** | |||||
| * A field with a value of either FAT, FAT12 or FAT16, | |||||
| * depending on the disk format. | |||||
| */ | |||||
| char fileSystemType[8]; | char fileSystemType[8]; | ||||
| /** X86 boot code */ | |||||
| /** X86 boot code */ | |||||
| uint8_t bootCode[448]; | uint8_t bootCode[448]; | ||||
| /** must be 0X55 */ | |||||
| /** must be 0X55 */ | |||||
| uint8_t bootSectorSig0; | uint8_t bootSectorSig0; | ||||
| /** must be 0XAA */ | |||||
| /** must be 0XAA */ | |||||
| uint8_t bootSectorSig1; | uint8_t bootSectorSig1; | ||||
| }__attribute__((packed)); | |||||
| } __attribute__((packed)); | |||||
| /** Type name for FAT Boot Sector */ | /** Type name for FAT Boot Sector */ | ||||
| typedef struct fat_boot fat_boot_t; | typedef struct fat_boot fat_boot_t; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| * | * | ||||
| */ | */ | ||||
| struct fat32_boot { | struct fat32_boot { | ||||
| /** | |||||
| * The first three bytes of the boot sector must be valid, | |||||
| * executable x 86-based CPU instructions. This includes a | |||||
| * jump instruction that skips the next non-executable bytes. | |||||
| */ | |||||
| /** | |||||
| * The first three bytes of the boot sector must be valid, | |||||
| * executable x 86-based CPU instructions. This includes a | |||||
| * jump instruction that skips the next non-executable bytes. | |||||
| */ | |||||
| uint8_t jump[3]; | uint8_t jump[3]; | ||||
| /** | |||||
| * This is typically a string of characters that identifies | |||||
| * the operating system that formatted the volume. | |||||
| */ | |||||
| /** | |||||
| * This is typically a string of characters that identifies | |||||
| * the operating system that formatted the volume. | |||||
| */ | |||||
| char oemId[8]; | char oemId[8]; | ||||
| /** | |||||
| * The size of a hardware sector. Valid decimal values for this | |||||
| * field are 512, 1024, 2048, and 4096. For most disks used in | |||||
| * the United States, the value of this field is 512. | |||||
| */ | |||||
| /** | |||||
| * The size of a hardware sector. Valid decimal values for this | |||||
| * field are 512, 1024, 2048, and 4096. For most disks used in | |||||
| * the United States, the value of this field is 512. | |||||
| */ | |||||
| uint16_t bytesPerSector; | 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. 128 should be avoided. | |||||
| */ | |||||
| /** | |||||
| * 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. 128 should be avoided. | |||||
| */ | |||||
| uint8_t sectorsPerCluster; | uint8_t sectorsPerCluster; | ||||
| /** | |||||
| * The number of sectors preceding the start of the first FAT, | |||||
| * including the boot sector. Must not be zero | |||||
| */ | |||||
| /** | |||||
| * The number of sectors preceding the start of the first FAT, | |||||
| * including the boot sector. Must not be zero | |||||
| */ | |||||
| uint16_t reservedSectorCount; | uint16_t reservedSectorCount; | ||||
| /** | |||||
| * The number of copies of the FAT on the volume. | |||||
| * The value of this field is always 2. | |||||
| */ | |||||
| /** | |||||
| * The number of copies of the FAT on the volume. | |||||
| * The value of this field is always 2. | |||||
| */ | |||||
| uint8_t fatCount; | uint8_t fatCount; | ||||
| /** | |||||
| * FAT12/FAT16 only. For FAT32 volumes, this field must be set to 0. | |||||
| */ | |||||
| /** | |||||
| * FAT12/FAT16 only. For FAT32 volumes, this field must be set to 0. | |||||
| */ | |||||
| uint16_t rootDirEntryCount; | uint16_t rootDirEntryCount; | ||||
| /** | |||||
| * For FAT32 volumes, this field must be 0. | |||||
| */ | |||||
| /** | |||||
| * For FAT32 volumes, this field must be 0. | |||||
| */ | |||||
| uint16_t totalSectors16; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | uint8_t mediaType; | ||||
| /** | |||||
| * On FAT32 volumes this field must be 0, and sectorsPerFat32 | |||||
| * contains the FAT size count. | |||||
| */ | |||||
| /** | |||||
| * On FAT32 volumes this field must be 0, and sectorsPerFat32 | |||||
| * contains the FAT size count. | |||||
| */ | |||||
| uint16_t sectorsPerFat16; | uint16_t sectorsPerFat16; | ||||
| /** Sectors per track for interrupt 0x13. Not used otherwise. */ | |||||
| /** Sectors per track for interrupt 0x13. Not used otherwise. */ | |||||
| uint16_t sectorsPerTrack; | uint16_t sectorsPerTrack; | ||||
| /** Number of heads for interrupt 0x13. Not used otherwise. */ | |||||
| /** Number of heads for interrupt 0x13. Not used otherwise. */ | |||||
| uint16_t headCount; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | uint32_t hidddenSectors; | ||||
| /** | |||||
| * Contains the total number of sectors in the FAT32 volume. | |||||
| */ | |||||
| /** | |||||
| * Contains the total number of sectors in the FAT32 volume. | |||||
| */ | |||||
| uint32_t totalSectors32; | uint32_t totalSectors32; | ||||
| /** | |||||
| * Count of sectors occupied by one FAT on FAT32 volumes. | |||||
| */ | |||||
| /** | |||||
| * Count of sectors occupied by one FAT on FAT32 volumes. | |||||
| */ | |||||
| uint32_t sectorsPerFat32; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | uint16_t fat32Flags; | ||||
| /** | |||||
| * FAT32 version. High byte is major revision number. | |||||
| * Low byte is minor revision number. Only 0.0 define. | |||||
| */ | |||||
| /** | |||||
| * FAT32 version. High byte is major revision number. | |||||
| * Low byte is minor revision number. Only 0.0 define. | |||||
| */ | |||||
| uint16_t fat32Version; | uint16_t fat32Version; | ||||
| /** | |||||
| * Cluster number of the first cluster of the root directory for FAT32. | |||||
| * This usually 2 but not required to be 2. | |||||
| */ | |||||
| /** | |||||
| * Cluster number of the first cluster of the root directory for FAT32. | |||||
| * This usually 2 but not required to be 2. | |||||
| */ | |||||
| uint32_t fat32RootCluster; | uint32_t fat32RootCluster; | ||||
| /** | |||||
| * Sector number of FSINFO structure in the reserved area of the | |||||
| * FAT32 volume. Usually 1. | |||||
| */ | |||||
| /** | |||||
| * Sector number of FSINFO structure in the reserved area of the | |||||
| * FAT32 volume. Usually 1. | |||||
| */ | |||||
| uint16_t fat32FSInfo; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | uint16_t fat32BackBootBlock; | ||||
| /** | |||||
| * Reserved for future expansion. Code that formats FAT32 volumes | |||||
| * should always set all of the bytes of this field to 0. | |||||
| */ | |||||
| /** | |||||
| * 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]; | uint8_t fat32Reserved[12]; | ||||
| /** | |||||
| * Related to the BIOS physical drive number. Floppy drives are | |||||
| * identified as 0x00 and physical hard disks are identified as | |||||
| * 0x80, regardless of the number of physical disk drives. | |||||
| * Typically, this value is set prior to issuing an INT 13h BIOS | |||||
| * call to specify the device to access. The value is only | |||||
| * relevant if the device is a boot device. | |||||
| */ | |||||
| /** | |||||
| * Related to the BIOS physical drive number. Floppy drives are | |||||
| * identified as 0x00 and physical hard disks are identified as | |||||
| * 0x80, regardless of the number of physical disk drives. | |||||
| * Typically, this value is set prior to issuing an INT 13h BIOS | |||||
| * call to specify the device to access. The value is only | |||||
| * relevant if the device is a boot device. | |||||
| */ | |||||
| uint8_t driveNumber; | uint8_t driveNumber; | ||||
| /** used by Windows NT - should be zero for FAT */ | |||||
| /** used by Windows NT - should be zero for FAT */ | |||||
| uint8_t reserved1; | uint8_t reserved1; | ||||
| /** 0X29 if next three fields are valid */ | |||||
| /** 0X29 if next three fields are valid */ | |||||
| uint8_t bootSignature; | uint8_t bootSignature; | ||||
| /** | |||||
| * A random serial number created when formatting a disk, | |||||
| * which helps to distinguish between disks. | |||||
| * Usually generated by combining date and time. | |||||
| */ | |||||
| /** | |||||
| * A random serial number created when formatting a disk, | |||||
| * which helps to distinguish between disks. | |||||
| * Usually generated by combining date and time. | |||||
| */ | |||||
| uint32_t volumeSerialNumber; | uint32_t volumeSerialNumber; | ||||
| /** | |||||
| * A field once used to store the volume label. The volume label | |||||
| * is now stored as a special file in the root directory. | |||||
| */ | |||||
| /** | |||||
| * A field once used to store the volume label. The volume label | |||||
| * is now stored as a special file in the root directory. | |||||
| */ | |||||
| char volumeLabel[11]; | char volumeLabel[11]; | ||||
| /** | |||||
| * A text field with a value of FAT32. | |||||
| */ | |||||
| /** | |||||
| * A text field with a value of FAT32. | |||||
| */ | |||||
| char fileSystemType[8]; | char fileSystemType[8]; | ||||
| /** X86 boot code */ | |||||
| /** X86 boot code */ | |||||
| uint8_t bootCode[420]; | uint8_t bootCode[420]; | ||||
| /** must be 0X55 */ | |||||
| /** must be 0X55 */ | |||||
| uint8_t bootSectorSig0; | uint8_t bootSectorSig0; | ||||
| /** must be 0XAA */ | |||||
| /** must be 0XAA */ | |||||
| uint8_t bootSectorSig1; | uint8_t bootSectorSig1; | ||||
| }__attribute__((packed)); | |||||
| } __attribute__((packed)); | |||||
| /** Type name for FAT32 Boot Sector */ | /** Type name for FAT32 Boot Sector */ | ||||
| typedef struct fat32_boot fat32_boot_t; | typedef struct fat32_boot fat32_boot_t; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| * | * | ||||
| */ | */ | ||||
| struct fat32_fsinfo { | struct fat32_fsinfo { | ||||
| /** must be 0X52, 0X52, 0X61, 0X41 */ | |||||
| /** must be 0X52, 0X52, 0X61, 0X41 */ | |||||
| uint32_t leadSignature; | uint32_t leadSignature; | ||||
| /** must be zero */ | |||||
| /** must be zero */ | |||||
| uint8_t reserved1[480]; | uint8_t reserved1[480]; | ||||
| /** must be 0X72, 0X72, 0X41, 0X61 */ | |||||
| /** must be 0X72, 0X72, 0X41, 0X61 */ | |||||
| uint32_t structSignature; | uint32_t structSignature; | ||||
| /** | |||||
| * Contains the last known free cluster count on the volume. | |||||
| * If the value is 0xFFFFFFFF, then the free count is unknown | |||||
| * and must be computed. Any other value can be used, but is | |||||
| * not necessarily correct. It should be range checked at least | |||||
| * to make sure it is <= volume cluster count. | |||||
| */ | |||||
| /** | |||||
| * Contains the last known free cluster count on the volume. | |||||
| * If the value is 0xFFFFFFFF, then the free count is unknown | |||||
| * and must be computed. Any other value can be used, but is | |||||
| * not necessarily correct. It should be range checked at least | |||||
| * to make sure it is <= volume cluster count. | |||||
| */ | |||||
| uint32_t freeCount; | uint32_t freeCount; | ||||
| /** | |||||
| * This is a hint for the FAT driver. It indicates the cluster | |||||
| * number at which the driver should start looking for free clusters. | |||||
| * If the value is 0xFFFFFFFF, then there is no hint and the driver | |||||
| * should start looking at cluster 2. | |||||
| */ | |||||
| /** | |||||
| * This is a hint for the FAT driver. It indicates the cluster | |||||
| * number at which the driver should start looking for free clusters. | |||||
| * If the value is 0xFFFFFFFF, then there is no hint and the driver | |||||
| * should start looking at cluster 2. | |||||
| */ | |||||
| uint32_t nextFree; | uint32_t nextFree; | ||||
| /** must be zero */ | |||||
| /** must be zero */ | |||||
| uint8_t reserved2[12]; | uint8_t reserved2[12]; | ||||
| /** must be 0X00, 0X00, 0X55, 0XAA */ | |||||
| /** must be 0X00, 0X00, 0X55, 0XAA */ | |||||
| uint8_t tailSignature[4]; | uint8_t tailSignature[4]; | ||||
| }__attribute__((packed)); | |||||
| } __attribute__((packed)); | |||||
| /** Type name for FAT32 FSINFO Sector */ | /** Type name for FAT32 FSINFO Sector */ | ||||
| typedef struct fat32_fsinfo fat32_fsinfo_t; | typedef struct fat32_fsinfo fat32_fsinfo_t; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| * \brief FAT short directory entry | * \brief FAT short directory entry | ||||
| * | * | ||||
| * Short means short 8.3 name, not the entry size. | * 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 | |||||
| * | |||||
| * 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 | * 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 | |||||
| * format (bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the | |||||
| * 16-bit word): | * 16-bit word): | ||||
| * | |||||
| * Bits 9-15: Count of years from 1980, valid value range 0-127 | |||||
| * | |||||
| * Bits 9-15: Count of years from 1980, valid value range 0-127 | |||||
| * inclusive (1980-2107). | * inclusive (1980-2107). | ||||
| * | |||||
| * | |||||
| * Bits 5-8: Month of year, 1 = January, valid value range 1-12 inclusive. | * 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. | * 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 | * 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 | |||||
| * 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). | * 16-bit word, bit 15 is the MSB of the 16-bit word). | ||||
| * | |||||
| * | |||||
| * Bits 11-15: Hours, valid value range 0-23 inclusive. | * Bits 11-15: Hours, valid value range 0-23 inclusive. | ||||
| * | |||||
| * | |||||
| * Bits 5-10: Minutes, valid value range 0-59 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). | * 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. | * The valid time range is from Midnight 00:00:00 to 23:59:58. | ||||
| */ | */ | ||||
| struct directoryEntry { | 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. | |||||
| */ | |||||
| /** 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]; | 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_. | |||||
| */ | |||||
| /** 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; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | 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) | |||||
| */ | |||||
| /** | |||||
| * 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; | uint8_t creationTimeTenths; | ||||
| /** Time file was created. */ | |||||
| /** Time file was created. */ | |||||
| uint16_t creationTime; | uint16_t creationTime; | ||||
| /** Date file was created. */ | |||||
| /** Date file was created. */ | |||||
| uint16_t creationDate; | 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. | |||||
| */ | |||||
| /** | |||||
| * 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; | uint16_t lastAccessDate; | ||||
| /** | |||||
| * High word of this entry's first cluster number (always 0 for a | |||||
| * FAT12 or FAT16 volume). | |||||
| */ | |||||
| /** | |||||
| * High word of this entry's first cluster number (always 0 for a | |||||
| * FAT12 or FAT16 volume). | |||||
| */ | |||||
| uint16_t firstClusterHigh; | uint16_t firstClusterHigh; | ||||
| /** Time of last write. File creation is considered a write. */ | |||||
| /** Time of last write. File creation is considered a write. */ | |||||
| uint16_t lastWriteTime; | uint16_t lastWriteTime; | ||||
| /** Date of last write. File creation is considered a write. */ | |||||
| /** Date of last write. File creation is considered a write. */ | |||||
| uint16_t lastWriteDate; | uint16_t lastWriteDate; | ||||
| /** Low word of this entry's first cluster number. */ | |||||
| /** Low word of this entry's first cluster number. */ | |||||
| uint16_t firstClusterLow; | uint16_t firstClusterLow; | ||||
| /** 32-bit unsigned holding this file's size in bytes. */ | |||||
| /** 32-bit unsigned holding this file's size in bytes. */ | |||||
| uint32_t fileSize; | uint32_t fileSize; | ||||
| }__attribute__((packed)); | |||||
| } __attribute__((packed)); | |||||
| /** Type name for directoryEntry */ | /** Type name for directoryEntry */ | ||||
| typedef struct directoryEntry dir_t; | typedef struct directoryEntry dir_t; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** Mask for file/subdirectory tests */ | /** Mask for file/subdirectory tests */ | ||||
| uint8_t const DIR_ATT_FILE_TYPE_MASK = (DIR_ATT_VOLUME_ID | DIR_ATT_DIRECTORY); | uint8_t const DIR_ATT_FILE_TYPE_MASK = (DIR_ATT_VOLUME_ID | DIR_ATT_DIRECTORY); | ||||
| /** Filename base-name is all lower case */ | |||||
| const uint8_t DIR_NT_LC_BASE = 0X08; | |||||
| /** Filename extension is all lower case.*/ | |||||
| const uint8_t DIR_NT_LC_EXT = 0X10; | |||||
| /** Directory entry is for a file | /** Directory entry is for a file | ||||
| * \param[in] dir Pointer to a directory entry. | * \param[in] dir Pointer to a directory entry. | ||||
| * | * | ||||
| * \return true if the entry is for part of a long name else false. | * \return true if the entry is for part of a long name else false. | ||||
| */ | */ | ||||
| static inline uint8_t DIR_IS_LONG_NAME(const dir_t* dir) { | static inline uint8_t DIR_IS_LONG_NAME(const dir_t* dir) { | ||||
| return (dir->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME; | |||||
| return dir->attributes == DIR_ATT_LONG_NAME; | |||||
| } | } | ||||
| /** Directory entry is hidden | |||||
| /** Directory entry is hidden | |||||
| * \param[in] dir Pointer to a directory entry. | * \param[in] dir Pointer to a directory entry. | ||||
| * | * | ||||
| * \return true if the entry is hidden else false. | * \return true if the entry is hidden else false. | ||||
| static inline uint8_t DIR_IS_SUBDIR(const dir_t* dir) { | static inline uint8_t DIR_IS_SUBDIR(const dir_t* dir) { | ||||
| return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == DIR_ATT_DIRECTORY; | return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == DIR_ATT_DIRECTORY; | ||||
| } | } | ||||
| /** Directory entry is system type | |||||
| /** Directory entry is system type | |||||
| * \param[in] dir Pointer to a directory entry. | * \param[in] dir Pointer to a directory entry. | ||||
| * | * | ||||
| * \return true if the entry is system else false. | * \return true if the entry is system else false. | ||||
| * \brief FAT long directory entry | * \brief FAT long directory entry | ||||
| */ | */ | ||||
| struct longDirectoryEntry { | struct longDirectoryEntry { | ||||
| /** | |||||
| * The order of this entry in the sequence of long dir entries | |||||
| * associated with the short dir entry at the end of the long dir set. | |||||
| * | |||||
| * If masked with 0x40 (LAST_LONG_ENTRY), this indicates the | |||||
| * entry is the last long dir entry in a set of long dir entries. | |||||
| * All valid sets of long dir entries must begin with an entry having | |||||
| * this mask. | |||||
| */ | |||||
| /** | |||||
| * The order of this entry in the sequence of long dir entries | |||||
| * associated with the short dir entry at the end of the long dir set. | |||||
| * | |||||
| * If masked with 0X40 (LAST_LONG_ENTRY), this indicates the | |||||
| * entry is the last long dir entry in a set of long dir entries. | |||||
| * All valid sets of long dir entries must begin with an entry having | |||||
| * this mask. | |||||
| */ | |||||
| uint8_t ord; | uint8_t ord; | ||||
| /** Characters 1-5 of the long-name sub-component in this entry. */ | |||||
| /** Characters 1-5 of the long-name sub-component in this entry. */ | |||||
| uint16_t name1[LDIR_NAME1_DIM]; | uint16_t name1[LDIR_NAME1_DIM]; | ||||
| /** Attributes - must be ATTR_LONG_NAME */ | |||||
| /** Attributes - must be ATTR_LONG_NAME */ | |||||
| uint8_t attr; | uint8_t attr; | ||||
| /** | |||||
| * If zero, indicates a directory entry that is a sub-component of a | |||||
| * long name. NOTE: Other values reserved for future extensions. | |||||
| * | |||||
| * Non-zero implies other directory entry types. | |||||
| */ | |||||
| /** | |||||
| * If zero, indicates a directory entry that is a sub-component of a | |||||
| * long name. NOTE: Other values reserved for future extensions. | |||||
| * | |||||
| * Non-zero implies other directory entry types. | |||||
| */ | |||||
| uint8_t type; | uint8_t type; | ||||
| /** | |||||
| * Checksum of name in the short dir entry at the end of the | |||||
| * long dir set. | |||||
| */ | |||||
| /** | |||||
| * Checksum of name in the short dir entry at the end of the | |||||
| * long dir set. | |||||
| */ | |||||
| uint8_t chksum; | uint8_t chksum; | ||||
| /** Characters 6-11 of the long-name sub-component in this entry. */ | |||||
| /** Characters 6-11 of the long-name sub-component in this entry. */ | |||||
| uint16_t name2[LDIR_NAME2_DIM]; | uint16_t name2[LDIR_NAME2_DIM]; | ||||
| /** Must be ZERO. This is an artifact of the FAT "first cluster" */ | |||||
| /** Must be ZERO. This is an artifact of the FAT "first cluster" */ | |||||
| uint16_t mustBeZero; | uint16_t mustBeZero; | ||||
| /** Characters 6-11 of the long-name sub-component in this entry. */ | |||||
| /** Characters 12 and 13 of the long-name sub-component in this entry. */ | |||||
| uint16_t name3[LDIR_NAME3_DIM]; | uint16_t name3[LDIR_NAME3_DIM]; | ||||
| }__attribute__((packed)); | |||||
| } __attribute__((packed)); | |||||
| /** Type name for longDirectoryEntry */ | /** Type name for longDirectoryEntry */ | ||||
| typedef struct longDirectoryEntry ldir_t; | typedef struct longDirectoryEntry ldir_t; | ||||
| /** | /** | ||||
| * begin with an entry having this mask. | * begin with an entry having this mask. | ||||
| */ | */ | ||||
| const uint8_t LDIR_ORD_LAST_LONG_ENTRY = 0X40; | const uint8_t LDIR_ORD_LAST_LONG_ENTRY = 0X40; | ||||
| /** | |||||
| * Fetch a 16-bit long file name character. | |||||
| * | |||||
| * \param[in] ldir Pointer to long file name directory entry. | |||||
| * \param[in] i Index of character to return; | |||||
| * \return The 16-bit field. | |||||
| */ | |||||
| inline uint16_t lfnChar(ldir_t *ldir, uint8_t i) { | |||||
| return i < LDIR_NAME1_DIM ? ldir->name1[i] : | |||||
| i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM) ? | |||||
| ldir->name2[i - LDIR_NAME1_DIM] : | |||||
| ldir->name3[i - (LDIR_NAME1_DIM + LDIR_NAME2_DIM)]; | |||||
| } | |||||
| #endif // FatStructs_h | #endif // FatStructs_h |
| * along with the FatLib Library. If not, see | * along with the FatLib Library. If not, see | ||||
| * <http://www.gnu.org/licenses/>. | * <http://www.gnu.org/licenses/>. | ||||
| */ | */ | ||||
| #include <string.h> | |||||
| #include "FatVolume.h" | #include "FatVolume.h" | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| cache_t* FatCache::read(uint32_t lbn, uint8_t option) { | cache_t* FatCache::read(uint32_t lbn, uint8_t option) { | ||||
| m_status |= option & CACHE_STATUS_MASK; | m_status |= option & CACHE_STATUS_MASK; | ||||
| return &m_block; | return &m_block; | ||||
| fail: | |||||
| fail: | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| } | } | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| while (1) { | while (1) { | ||||
| find++; | find++; | ||||
| // If at end of FAT go to beginning of FAT. | // If at end of FAT go to beginning of FAT. | ||||
| if (find > m_lastCluster) find = 2; | |||||
| if (find > m_lastCluster) { | |||||
| find = 2; | |||||
| } | |||||
| uint32_t f; | uint32_t f; | ||||
| if (!fatGet(find, &f)) { | |||||
| int8_t fg = fatGet(find, &f); | |||||
| if (fg < 0) { | |||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| if (f == 0) break; | |||||
| if (fg && f == 0) { | |||||
| break; | |||||
| } | |||||
| if (find == start) { | if (find == start) { | ||||
| // Can't find space checked all clusters. | // Can't find space checked all clusters. | ||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| *next = find; | *next = find; | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| bgnCluster = endCluster = 2; | bgnCluster = endCluster = 2; | ||||
| } | } | ||||
| uint32_t f; | uint32_t f; | ||||
| if (!fatGet(endCluster, &f)) { | |||||
| int8_t fg = fatGet(endCluster, &f); | |||||
| if (fg < 0) { | |||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| if (f != 0) { | |||||
| // don't update search start if unallocated clusters before endCluster. | |||||
| if (bgnCluster != endCluster) setStart = false; | |||||
| if (f || fg == 0) { | |||||
| // cluster in use try next cluster as bgnCluster | // cluster in use try next cluster as bgnCluster | ||||
| bgnCluster = endCluster + 1; | bgnCluster = endCluster + 1; | ||||
| // don't update search start if unallocated clusters before endCluster. | |||||
| if (bgnCluster != endCluster) { | |||||
| setStart = false; | |||||
| } | |||||
| } else if ((endCluster - bgnCluster + 1) == count) { | } else if ((endCluster - bgnCluster + 1) == count) { | ||||
| // done - found space | // done - found space | ||||
| break; | break; | ||||
| endCluster++; | endCluster++; | ||||
| } | } | ||||
| // remember possible next free cluster | // remember possible next free cluster | ||||
| if (setStart) m_allocSearchStart = endCluster + 1; | |||||
| if (setStart) { | |||||
| m_allocSearchStart = endCluster + 1; | |||||
| } | |||||
| // mark end of chain | // mark end of chain | ||||
| if (!fatPutEOC(endCluster)) { | if (!fatPutEOC(endCluster)) { | ||||
| *firstCluster = bgnCluster; | *firstCluster = bgnCluster; | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| return m_dataStartBlock + ((cluster - 2) << m_clusterSizeShift); | return m_dataStartBlock + ((cluster - 2) << m_clusterSizeShift); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // Fetch a FAT entry | |||||
| bool FatVolume::fatGet(uint32_t cluster, uint32_t* value) { | |||||
| // Fetch a FAT entry - return -1 error, 0 EOC, else 1. | |||||
| int8_t FatVolume::fatGet(uint32_t cluster, uint32_t* value) { | |||||
| uint32_t lba; | uint32_t lba; | ||||
| uint32_t next; | |||||
| cache_t* pc; | cache_t* pc; | ||||
| // error if reserved cluster of beyond FAT | // error if reserved cluster of beyond FAT | ||||
| if (cluster < 2 || cluster > m_lastCluster) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| DBG_HALT_IF(cluster < 2 || cluster > m_lastCluster); | |||||
| if (m_fatType == 32) { | if (m_fatType == 32) { | ||||
| lba = m_fatStartBlock + (cluster >> 7); | lba = m_fatStartBlock + (cluster >> 7); | ||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| *value = pc->fat32[cluster & 0X7F] & FAT32MASK; | |||||
| return true; | |||||
| next = pc->fat32[cluster & 0X7F] & FAT32MASK; | |||||
| goto done; | |||||
| } | } | ||||
| if (m_fatType == 16) { | if (m_fatType == 16) { | ||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| *value = pc->fat16[cluster & 0XFF]; | |||||
| return true; | |||||
| next = pc->fat16[cluster & 0XFF]; | |||||
| goto done; | |||||
| } | } | ||||
| if (FAT12_SUPPORT && m_fatType == 12) { | if (FAT12_SUPPORT && m_fatType == 12) { | ||||
| uint16_t index = cluster; | uint16_t index = cluster; | ||||
| index = 0; | index = 0; | ||||
| } | } | ||||
| tmp |= pc->data[index] << 8; | tmp |= pc->data[index] << 8; | ||||
| *value = cluster & 1 ? tmp >> 4 : tmp & 0XFFF; | |||||
| return true; | |||||
| next = cluster & 1 ? tmp >> 4 : tmp & 0XFFF; | |||||
| goto done; | |||||
| } else { | } else { | ||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| done: | |||||
| if (isEOC(next)) { | |||||
| return 0; | |||||
| } | |||||
| *value = next; | |||||
| return 1; | |||||
| fail: | |||||
| return false; | |||||
| fail: | |||||
| return -1; | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // Store a FAT entry | // Store a FAT entry | ||||
| cache_t* pc; | cache_t* pc; | ||||
| // error if reserved cluster of beyond FAT | // error if reserved cluster of beyond FAT | ||||
| if (cluster < 2 || cluster > m_lastCluster) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| DBG_HALT_IF(cluster < 2 || cluster > m_lastCluster); | |||||
| if (m_fatType == 32) { | if (m_fatType == 32) { | ||||
| lba = m_fatStartBlock + (cluster >> 7); | |||||
| lba = m_fatStartBlock + (cluster >> 7); | |||||
| pc = cacheFetchFat(lba, FatCache::CACHE_FOR_WRITE); | pc = cacheFetchFat(lba, FatCache::CACHE_FOR_WRITE); | ||||
| if (!pc) { | if (!pc) { | ||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| fail: | |||||
| fail: | |||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // free a cluster chain | // free a cluster chain | ||||
| bool FatVolume::freeChain(uint32_t cluster) { | bool FatVolume::freeChain(uint32_t cluster) { | ||||
| uint32_t next; | uint32_t next; | ||||
| int8_t fg; | |||||
| do { | do { | ||||
| if (!fatGet(cluster, &next)) { | |||||
| fg = fatGet(cluster, &next); | |||||
| if (fg < 0) { | |||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| if (cluster < m_allocSearchStart) m_allocSearchStart = cluster; | |||||
| if (cluster < m_allocSearchStart) { | |||||
| m_allocSearchStart = cluster; | |||||
| } | |||||
| cluster = next; | cluster = next; | ||||
| } while (!isEOC(cluster)); | |||||
| } while (fg); | |||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| if (FAT12_SUPPORT && m_fatType == 12) { | if (FAT12_SUPPORT && m_fatType == 12) { | ||||
| for (unsigned i = 2; i < todo; i++) { | for (unsigned i = 2; i < todo; i++) { | ||||
| uint32_t c; | uint32_t c; | ||||
| if (!fatGet(i, &c)) { | |||||
| int8_t fg = fatGet(i, &c); | |||||
| if (fg < 0) { | |||||
| DBG_FAIL_MACRO; | DBG_FAIL_MACRO; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| if (c == 0) free++; | |||||
| if (fg && c == 0) { | |||||
| free++; | |||||
| } | |||||
| } | } | ||||
| } else if (m_fatType == 16 || m_fatType == 32) { | } else if (m_fatType == 16 || m_fatType == 32) { | ||||
| lba = m_fatStartBlock; | lba = m_fatStartBlock; | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| n = m_fatType == 16 ? 256 : 128; | n = m_fatType == 16 ? 256 : 128; | ||||
| if (todo < n) n = todo; | |||||
| if (todo < n) { | |||||
| n = todo; | |||||
| } | |||||
| if (m_fatType == 16) { | if (m_fatType == 16) { | ||||
| for (uint16_t i = 0; i < n; i++) { | for (uint16_t i = 0; i < n; i++) { | ||||
| if (pc->fat16[i] == 0) free++; | |||||
| if (pc->fat16[i] == 0) { | |||||
| free++; | |||||
| } | |||||
| } | } | ||||
| } else { | } else { | ||||
| for (uint16_t i = 0; i < n; i++) { | for (uint16_t i = 0; i < n; i++) { | ||||
| if (pc->fat32[i] == 0) free++; | |||||
| if (pc->fat32[i] == 0) { | |||||
| free++; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| todo -= n; | todo -= n; | ||||
| } | } | ||||
| return free; | return free; | ||||
| fail: | |||||
| fail: | |||||
| return -1; | return -1; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| } | } | ||||
| fbs = &(pc->fbs32); | fbs = &(pc->fbs32); | ||||
| if (fbs->bytesPerSector != 512 || | if (fbs->bytesPerSector != 512 || | ||||
| fbs->fatCount != 2 || | |||||
| fbs->reservedSectorCount == 0) { | |||||
| // not valid FAT volume | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| fbs->fatCount != 2 || | |||||
| fbs->reservedSectorCount == 0) { | |||||
| // not valid FAT volume | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | } | ||||
| m_blocksPerCluster = fbs->sectorsPerCluster; | m_blocksPerCluster = fbs->sectorsPerCluster; | ||||
| m_clusterBlockMask = m_blocksPerCluster - 1; | m_clusterBlockMask = m_blocksPerCluster - 1; | ||||
| } | } | ||||
| m_blocksPerFat = fbs->sectorsPerFat16 ? | m_blocksPerFat = fbs->sectorsPerFat16 ? | ||||
| fbs->sectorsPerFat16 : fbs->sectorsPerFat32; | |||||
| fbs->sectorsPerFat16 : fbs->sectorsPerFat32; | |||||
| m_fatStartBlock = volumeStartBlock + fbs->reservedSectorCount; | m_fatStartBlock = volumeStartBlock + fbs->reservedSectorCount; | ||||
| // total blocks for FAT16 or FAT32 | // total blocks for FAT16 or FAT32 | ||||
| totalBlocks = fbs->totalSectors16 ? | totalBlocks = fbs->totalSectors16 ? | ||||
| fbs->totalSectors16 : fbs->totalSectors32; | |||||
| fbs->totalSectors16 : fbs->totalSectors32; | |||||
| // total data blocks | // total data blocks | ||||
| clusterCount = totalBlocks - (m_dataStartBlock - volumeStartBlock); | clusterCount = totalBlocks - (m_dataStartBlock - volumeStartBlock); | ||||
| // divide by cluster size to get cluster count | // divide by cluster size to get cluster count | ||||
| clusterCount >>= m_clusterSizeShift; | clusterCount >>= m_clusterSizeShift; | ||||
| m_lastCluster = clusterCount + 1; | m_lastCluster = clusterCount + 1; | ||||
| // FAT type is determined by cluster count | // FAT type is determined by cluster count | ||||
| if (clusterCount < 4085) { | if (clusterCount < 4085) { | ||||
| m_fatType = 12; | m_fatType = 12; | ||||
| } | } | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| return false; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| bool FatVolume::wipe(print_t* pr) { | |||||
| cache_t* cache; | |||||
| uint16_t count; | |||||
| uint32_t lbn; | |||||
| if (!m_fatType) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| cache = cacheClear(); | |||||
| if (!cache) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| memset(cache->data, 0, 512); | |||||
| // Zero root. | |||||
| if (m_fatType == 32) { | |||||
| lbn = clusterStartBlock(m_rootDirStart); | |||||
| count = m_blocksPerCluster; | |||||
| } else { | |||||
| lbn = m_rootDirStart; | |||||
| count = m_rootDirEntryCount/16; | |||||
| } | |||||
| for (uint32_t n = 0; n < count; n++) { | |||||
| if (!writeBlock(lbn + n, cache->data)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| } | |||||
| // Clear FATs. | |||||
| count = 2*m_blocksPerFat; | |||||
| lbn = m_fatStartBlock; | |||||
| for (uint32_t nb = 0; nb < count; nb++) { | |||||
| if (pr && (nb & 0XFF) == 0) { | |||||
| pr->write('.'); | |||||
| } | |||||
| if (!writeBlock(lbn + nb, cache->data)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| } | |||||
| // Reserve first two clusters. | |||||
| if (m_fatType == 32) { | |||||
| cache->fat32[0] = 0x0FFFFFF8; | |||||
| cache->fat32[1] = 0x0FFFFFFF; | |||||
| } else if (m_fatType == 16) { | |||||
| cache->fat16[0] = 0XFFF8; | |||||
| cache->fat16[1] = 0XFFFF; | |||||
| } else if (FAT12_SUPPORT && m_fatType == 12) { | |||||
| cache->fat32[0] = 0XFFFFF8; | |||||
| } else { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| if (!writeBlock(m_fatStartBlock, cache->data) || | |||||
| !writeBlock(m_fatStartBlock + m_blocksPerFat, cache->data)) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| if (m_fatType == 32) { | |||||
| // Reserve root cluster. | |||||
| if (!fatPutEOC(m_rootDirStart) || !cacheSync()) { | |||||
| DBG_FAIL_MACRO; | |||||
| goto fail; | |||||
| } | |||||
| } | |||||
| if (pr) { | |||||
| pr->write('\r'); | |||||
| pr->write('\n'); | |||||
| } | |||||
| m_fatType = 0; | |||||
| return true; | |||||
| fail: | |||||
| m_fatType = 0; | |||||
| return false; | return false; | ||||
| } | } |
| #include "FatLibConfig.h" | #include "FatLibConfig.h" | ||||
| #include "FatStructs.h" | #include "FatStructs.h" | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| #ifndef DOXYGEN_SHOULD_SKIP_THIS | |||||
| /** Macro for debug. */ | /** Macro for debug. */ | ||||
| // #include <Arduino.h> | |||||
| #define DBG_FAIL_MACRO // Serial.print(__FILE__);Serial.println(__LINE__) | |||||
| #define DEBUG_MODE 0 | |||||
| #if DEBUG_MODE | |||||
| #include <Arduino.h> | |||||
| #define DBG_FAIL_MACRO Serial.print(F(__FILE__)); Serial.println(__LINE__) | |||||
| #define DBG_PRINT_IF(b) if (b) {Serial.println(F(#b)); DBG_FAIL_MACRO;} | |||||
| #define DBG_HALT_IF(b) if (b) {Serial.println(F(#b));\ | |||||
| DBG_FAIL_MACRO; while (1);} | |||||
| #else // DEBUG_MODE | |||||
| #define DBG_FAIL_MACRO | |||||
| #define DBG_PRINT_IF(b) | |||||
| #define DBG_HALT_IF(b) | |||||
| #endif // DEBUG_MODE | |||||
| #endif // DOXYGEN_SHOULD_SKIP_THIS | |||||
| //------------------------------------------------------------------------------ | |||||
| #if defined(ARDUINO) || defined(DOXYGEN) | |||||
| #include <Arduino.h> | |||||
| /** Use Print on Arduino */ | |||||
| typedef Print print_t; | |||||
| #else // ARDUINO | |||||
| // Arduino type for flash string. | |||||
| class __FlashStringHelper; | |||||
| /** | |||||
| * \class CharWriter | |||||
| * \brief Character output - often serial port. | |||||
| */ | |||||
| class CharWriter { | |||||
| public: | |||||
| virtual size_t write(char c) = 0; | |||||
| virtual size_t write(const char* s) = 0; | |||||
| }; | |||||
| typedef Print print_t; | |||||
| #endif // ARDUINO | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // Forward declaration of FatVolume. | // Forward declaration of FatVolume. | ||||
| class FatVolume; | class FatVolume; | ||||
| * \brief Cache for an raw data block. | * \brief Cache for an raw data block. | ||||
| */ | */ | ||||
| union cache_t { | union cache_t { | ||||
| /** Used to access cached file data blocks. */ | |||||
| /** Used to access cached file data blocks. */ | |||||
| uint8_t data[512]; | uint8_t data[512]; | ||||
| /** Used to access cached FAT16 entries. */ | |||||
| /** Used to access cached FAT16 entries. */ | |||||
| uint16_t fat16[256]; | uint16_t fat16[256]; | ||||
| /** Used to access cached FAT32 entries. */ | |||||
| /** Used to access cached FAT32 entries. */ | |||||
| uint32_t fat32[128]; | uint32_t fat32[128]; | ||||
| /** Used to access cached directory entries. */ | |||||
| /** Used to access cached directory entries. */ | |||||
| dir_t dir[16]; | dir_t dir[16]; | ||||
| /** Used to access a cached Master Boot Record. */ | |||||
| /** Used to access a cached Master Boot Record. */ | |||||
| mbr_t mbr; | mbr_t mbr; | ||||
| /** Used to access to a cached FAT boot sector. */ | |||||
| /** Used to access to a cached FAT boot sector. */ | |||||
| fat_boot_t fbs; | fat_boot_t fbs; | ||||
| /** Used to access to a cached FAT32 boot sector. */ | |||||
| /** Used to access to a cached FAT32 boot sector. */ | |||||
| fat32_boot_t fbs32; | fat32_boot_t fbs32; | ||||
| /** Used to access to a cached FAT32 FSINFO sector. */ | |||||
| /** Used to access to a cached FAT32 FSINFO sector. */ | |||||
| fat32_fsinfo_t fsinfo; | fat32_fsinfo_t fsinfo; | ||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| static const uint8_t CACHE_STATUS_MIRROR_FAT = 2; | static const uint8_t CACHE_STATUS_MIRROR_FAT = 2; | ||||
| /** Cache block status bits */ | /** Cache block status bits */ | ||||
| static const uint8_t CACHE_STATUS_MASK | static const uint8_t CACHE_STATUS_MASK | ||||
| = CACHE_STATUS_DIRTY | CACHE_STATUS_MIRROR_FAT; | |||||
| = CACHE_STATUS_DIRTY | CACHE_STATUS_MIRROR_FAT; | |||||
| /** Sync existing block but do not read new block. */ | /** Sync existing block but do not read new block. */ | ||||
| static const uint8_t CACHE_OPTION_NO_READ = 4; | static const uint8_t CACHE_OPTION_NO_READ = 4; | ||||
| /** Cache block for read. */ | /** Cache block for read. */ | ||||
| static uint8_t const CACHE_FOR_WRITE = CACHE_STATUS_DIRTY; | static uint8_t const CACHE_FOR_WRITE = CACHE_STATUS_DIRTY; | ||||
| /** Reserve cache block for write - do not read from block device. */ | /** Reserve cache block for write - do not read from block device. */ | ||||
| static uint8_t const CACHE_RESERVE_FOR_WRITE | static uint8_t const CACHE_RESERVE_FOR_WRITE | ||||
| = CACHE_STATUS_DIRTY | CACHE_OPTION_NO_READ; | |||||
| = CACHE_STATUS_DIRTY | CACHE_OPTION_NO_READ; | |||||
| /** \return Cache block address. */ | /** \return Cache block address. */ | ||||
| cache_t* block() {return &m_block;} | |||||
| cache_t* block() { | |||||
| return &m_block; | |||||
| } | |||||
| /** Set current block dirty. */ | /** Set current block dirty. */ | ||||
| void dirty() {m_status |= CACHE_STATUS_DIRTY;} | |||||
| void dirty() { | |||||
| m_status |= CACHE_STATUS_DIRTY; | |||||
| } | |||||
| /** Initialize the cache. | /** Initialize the cache. | ||||
| * \param[in] vol FatVolume that owns this FatCache. | * \param[in] vol FatVolume that owns this FatCache. | ||||
| */ | */ | ||||
| m_lbn = 0XFFFFFFFF; | m_lbn = 0XFFFFFFFF; | ||||
| } | } | ||||
| /** \return Logical block number for cached block. */ | /** \return Logical block number for cached block. */ | ||||
| uint32_t lbn() {return m_lbn;} | |||||
| uint32_t lbn() { | |||||
| return m_lbn; | |||||
| } | |||||
| /** Read a block into the cache. | /** Read a block into the cache. | ||||
| * \param[in] lbn Block to read. | * \param[in] lbn Block to read. | ||||
| * \param[in] option mode for cached block. | * \param[in] option mode for cached block. | ||||
| FatVolume() : m_fatType(0) {} | FatVolume() : m_fatType(0) {} | ||||
| /** \return The volume's cluster size in blocks. */ | /** \return The volume's cluster size in blocks. */ | ||||
| uint8_t blocksPerCluster() const {return m_blocksPerCluster;} | |||||
| uint8_t blocksPerCluster() const { | |||||
| return m_blocksPerCluster; | |||||
| } | |||||
| /** \return The number of blocks in one FAT. */ | /** \return The number of blocks in one FAT. */ | ||||
| uint32_t blocksPerFat() const {return m_blocksPerFat;} | |||||
| uint32_t blocksPerFat() const { | |||||
| return m_blocksPerFat; | |||||
| } | |||||
| /** Clear the cache and returns a pointer to the cache. Not for normal apps. | /** Clear the cache and returns a pointer to the cache. Not for normal apps. | ||||
| * \return A pointer to the cache buffer or zero if an error occurs. | * \return A pointer to the cache buffer or zero if an error occurs. | ||||
| */ | */ | ||||
| cache_t* cacheClear() { | cache_t* cacheClear() { | ||||
| if (!cacheSync()) return 0; | |||||
| if (!cacheSync()) { | |||||
| return 0; | |||||
| } | |||||
| m_cache.invalidate(); | m_cache.invalidate(); | ||||
| return m_cache.block(); | return m_cache.block(); | ||||
| } | } | ||||
| /** \return The total number of clusters in the volume. */ | /** \return The total number of clusters in the volume. */ | ||||
| uint32_t clusterCount() const {return m_lastCluster - 1;} | |||||
| uint32_t clusterCount() const { | |||||
| return m_lastCluster - 1; | |||||
| } | |||||
| /** \return The shift count required to multiply by blocksPerCluster. */ | /** \return The shift count required to multiply by blocksPerCluster. */ | ||||
| uint8_t clusterSizeShift() const {return m_clusterSizeShift;} | |||||
| uint8_t clusterSizeShift() const { | |||||
| return m_clusterSizeShift; | |||||
| } | |||||
| /** \return The logical block number for the start of file data. */ | /** \return The logical block number for the start of file data. */ | ||||
| uint32_t dataStartBlock() const {return m_dataStartBlock;} | |||||
| uint32_t dataStartBlock() const { | |||||
| return m_dataStartBlock; | |||||
| } | |||||
| /** \return The number of File Allocation Tables. */ | /** \return The number of File Allocation Tables. */ | ||||
| uint8_t fatCount() {return 2;} | |||||
| uint8_t fatCount() { | |||||
| return 2; | |||||
| } | |||||
| /** \return The logical block number for the start of the first FAT. */ | /** \return The logical block number for the start of the first FAT. */ | ||||
| uint32_t fatStartBlock() const {return m_fatStartBlock;} | |||||
| uint32_t fatStartBlock() const { | |||||
| return m_fatStartBlock; | |||||
| } | |||||
| /** \return The FAT type of the volume. Values are 12, 16 or 32. */ | /** \return The FAT type of the volume. Values are 12, 16 or 32. */ | ||||
| uint8_t fatType() const {return m_fatType;} | |||||
| uint8_t fatType() const { | |||||
| return m_fatType; | |||||
| } | |||||
| /** Volume free space in clusters. | /** Volume free space in clusters. | ||||
| * | * | ||||
| * \return Count of free clusters for success or -1 if an error occurs. | * \return Count of free clusters for success or -1 if an error occurs. | ||||
| /** Initialize a FAT volume. Try partition one first then try super | /** Initialize a FAT volume. Try partition one first then try super | ||||
| * floppy format. | * floppy format. | ||||
| * | * | ||||
| * \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. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool init() { return init(1) ? true : init(0);} | |||||
| bool init() { | |||||
| return init(1) ? true : init(0); | |||||
| } | |||||
| /** Initialize a FAT volume. | /** Initialize a FAT volume. | ||||
| * \param[in] part The partition to be used. Legal values for \a part are | * \param[in] part The partition to be used. Legal values for \a part are | ||||
| * a MBR, Master Boot Record, or zero if the device is formatted as | * a MBR, Master Boot Record, or zero if the device is formatted as | ||||
| * a super floppy with the FAT boot sector in block zero. | * 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. | |||||
| * \return The value true is returned for success and | |||||
| * the value false is returned for failure. | |||||
| */ | */ | ||||
| bool init(uint8_t part); | bool init(uint8_t part); | ||||
| /** \return The number of entries in the root directory for FAT16 volumes. */ | /** \return The number of entries in the root directory for FAT16 volumes. */ | ||||
| uint32_t rootDirEntryCount() const {return m_rootDirEntryCount;} | |||||
| uint16_t rootDirEntryCount() const { | |||||
| return m_rootDirEntryCount; | |||||
| } | |||||
| /** \return The logical block number for the start of the root directory | /** \return The logical block number for the start of the root directory | ||||
| on FAT16 volumes or the first cluster number on FAT32 volumes. */ | on FAT16 volumes or the first cluster number on FAT32 volumes. */ | ||||
| uint32_t rootDirStart() const {return m_rootDirStart;} | |||||
| uint32_t rootDirStart() const { | |||||
| return m_rootDirStart; | |||||
| } | |||||
| /** Wipe all data from the volume. | |||||
| * \param[in] pr print stream for status dots. | |||||
| * \return true for success else false. | |||||
| */ | |||||
| bool wipe(print_t* pr = 0); | |||||
| /** Debug access to FAT table | /** Debug access to FAT table | ||||
| * | * | ||||
| * \param[in] n cluster number. | * \param[in] n cluster number. | ||||
| * \param[out] v value of entry | * \param[out] v value of entry | ||||
| * \return true for success or false for failure | * \return true for success or false for failure | ||||
| */ | */ | ||||
| bool dbgFat(uint32_t n, uint32_t* v) {return fatGet(n, v);} | |||||
| int8_t dbgFat(uint32_t n, uint32_t* v) { | |||||
| return fatGet(n, v); | |||||
| } | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| private: | private: | ||||
| // Allow FatFile and FatCache access to FatVolume private functions. | // Allow FatFile and FatCache access to FatVolume private functions. | ||||
| return m_fatCache.read(blockNumber, | return m_fatCache.read(blockNumber, | ||||
| options | FatCache::CACHE_STATUS_MIRROR_FAT); | options | FatCache::CACHE_STATUS_MIRROR_FAT); | ||||
| } | } | ||||
| bool cacheSync() {return m_cache.sync() && m_fatCache.sync();} | |||||
| bool cacheSync() { | |||||
| return m_cache.sync() && m_fatCache.sync(); | |||||
| } | |||||
| #else // | #else // | ||||
| cache_t* cacheFetchFat(uint32_t blockNumber, uint8_t options) { | cache_t* cacheFetchFat(uint32_t blockNumber, uint8_t options) { | ||||
| return cacheFetchData(blockNumber, | return cacheFetchData(blockNumber, | ||||
| options | FatCache::CACHE_STATUS_MIRROR_FAT); | options | FatCache::CACHE_STATUS_MIRROR_FAT); | ||||
| } | } | ||||
| bool cacheSync() {return m_cache.sync();} | |||||
| bool cacheSync() { | |||||
| return m_cache.sync(); | |||||
| } | |||||
| #endif // USE_SEPARATE_FAT_CACHE | #endif // USE_SEPARATE_FAT_CACHE | ||||
| cache_t* cacheFetchData(uint32_t blockNumber, uint8_t options) { | cache_t* cacheFetchData(uint32_t blockNumber, uint8_t options) { | ||||
| return m_cache.read(blockNumber, options); | return m_cache.read(blockNumber, options); | ||||
| void cacheInvalidate() { | void cacheInvalidate() { | ||||
| m_cache.invalidate(); | m_cache.invalidate(); | ||||
| } | } | ||||
| bool cacheSyncData() {return m_cache.sync();} | |||||
| cache_t *cacheAddress() {return m_cache.block();} | |||||
| uint32_t cacheBlockNumber() {return m_cache.lbn();} | |||||
| void cacheDirty() {m_cache.dirty();} | |||||
| bool cacheSyncData() { | |||||
| return m_cache.sync(); | |||||
| } | |||||
| cache_t *cacheAddress() { | |||||
| return m_cache.block(); | |||||
| } | |||||
| uint32_t cacheBlockNumber() { | |||||
| return m_cache.lbn(); | |||||
| } | |||||
| void cacheDirty() { | |||||
| m_cache.dirty(); | |||||
| } | |||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| bool allocateCluster(uint32_t current, uint32_t* next); | bool allocateCluster(uint32_t current, uint32_t* next); | ||||
| bool allocContiguous(uint32_t count, uint32_t* firstCluster); | bool allocContiguous(uint32_t count, uint32_t* firstCluster); | ||||
| uint8_t blockOfCluster(uint32_t position) const { | uint8_t blockOfCluster(uint32_t position) const { | ||||
| return (position >> 9) & m_clusterBlockMask;} | |||||
| return (position >> 9) & m_clusterBlockMask; | |||||
| } | |||||
| uint32_t clusterStartBlock(uint32_t cluster) const; | uint32_t clusterStartBlock(uint32_t cluster) const; | ||||
| bool fatGet(uint32_t cluster, uint32_t* value); | |||||
| int8_t fatGet(uint32_t cluster, uint32_t* value); | |||||
| bool fatPut(uint32_t cluster, uint32_t value); | bool fatPut(uint32_t cluster, uint32_t value); | ||||
| bool fatPutEOC(uint32_t cluster) { | bool fatPutEOC(uint32_t cluster) { | ||||
| return fatPut(cluster, 0x0FFFFFFF); | return fatPut(cluster, 0x0FFFFFFF); | ||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| // Virtual block I/O functions. | // Virtual block I/O functions. | ||||
| virtual bool readBlock(uint32_t block, uint8_t* dst) = 0; | virtual bool readBlock(uint32_t block, uint8_t* dst) = 0; | ||||
| virtual bool writeBlock(uint32_t block, const uint8_t* dst) = 0; | |||||
| virtual bool writeBlock(uint32_t block, const uint8_t* src) = 0; | |||||
| #if USE_MULTI_BLOCK_IO | #if USE_MULTI_BLOCK_IO | ||||
| virtual bool readBlocks(uint32_t block, uint8_t* dst, size_t nb) = 0; | virtual bool readBlocks(uint32_t block, uint8_t* dst, size_t nb) = 0; | ||||
| virtual bool writeBlocks(uint32_t block, const uint8_t* dst, size_t nb) = 0; | |||||
| virtual bool writeBlocks(uint32_t block, const uint8_t* src, size_t nb) = 0; | |||||
| #endif // USE_MULTI_BLOCK_IO | #endif // USE_MULTI_BLOCK_IO | ||||
| }; | }; | ||||
| #endif // FatVolume | #endif // FatVolume |
| n &= 63; | n &= 63; | ||||
| for (uint8_t i = 0; n; n >>= 1, i++) { | for (uint8_t i = 0; n; n >>= 1, i++) { | ||||
| #ifdef __AVR__ | #ifdef __AVR__ | ||||
| if (n & 1) v *= pgm_read_float(&s[i]); | |||||
| if (n & 1) { | |||||
| v *= pgm_read_float(&s[i]); | |||||
| } | |||||
| #else // __AVR__ | #else // __AVR__ | ||||
| if (n & 1) v *= s[i]; | |||||
| if (n & 1) { | |||||
| v *= s[i]; | |||||
| } | |||||
| #endif // __AVR__ | #endif // __AVR__ | ||||
| } | } | ||||
| return v; | return v; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| char* fmtFloat(float value, char* p, uint8_t prec) { | char* fmtFloat(float value, char* p, uint8_t prec) { | ||||
| char sign = value < 0 ? '-' : 0; | char sign = value < 0 ? '-' : 0; | ||||
| if (sign) value = -value; | |||||
| if (sign) { | |||||
| value = -value; | |||||
| } | |||||
| if (isnan(value)) { | if (isnan(value)) { | ||||
| *--p = 'n'; | *--p = 'n'; | ||||
| *--p = 'o'; | *--p = 'o'; | ||||
| return p; | return p; | ||||
| } | } | ||||
| if (prec > 9) prec = 9; | |||||
| if (prec > 9) { | |||||
| prec = 9; | |||||
| } | |||||
| value += scale10(0.5, -prec); | value += scale10(0.5, -prec); | ||||
| uint32_t whole = value; | uint32_t whole = value; | ||||
| char* tmp = p - prec; | char* tmp = p - prec; | ||||
| uint32_t fraction = scale10(value - whole, prec); | uint32_t fraction = scale10(value - whole, prec); | ||||
| p = fmtDec(fraction, p); | p = fmtDec(fraction, p); | ||||
| while (p > tmp) *--p = '0'; | |||||
| while (p > tmp) { | |||||
| *--p = '0'; | |||||
| } | |||||
| *--p = '.'; | *--p = '.'; | ||||
| } | } | ||||
| p = fmtDec(whole, p); | p = fmtDec(whole, p); | ||||
| if (sign) *--p = sign; | |||||
| if (sign) { | |||||
| *--p = sign; | |||||
| } | |||||
| return p; | return p; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| */ | */ | ||||
| char* fmtFloat(float value, char* ptr, uint8_t prec, char expChar) { | char* fmtFloat(float value, char* ptr, uint8_t prec, char expChar) { | ||||
| bool neg = value < 0; | bool neg = value < 0; | ||||
| if (neg) value = -value; | |||||
| if (neg) { | |||||
| value = -value; | |||||
| } | |||||
| // check for nan inf ovf | // check for nan inf ovf | ||||
| if (isnan(value)) { | if (isnan(value)) { | ||||
| *--ptr = 'o'; | *--ptr = 'o'; | ||||
| return ptr; | return ptr; | ||||
| } | } | ||||
| if (prec > 9) prec = 9; | |||||
| if (prec > 9) { | |||||
| prec = 9; | |||||
| } | |||||
| float round = scale10(0.5, -prec); | float round = scale10(0.5, -prec); | ||||
| if (expChar) { | if (expChar) { | ||||
| int8_t exp = 0; | int8_t exp = 0; | ||||
| exp++; | exp++; | ||||
| } | } | ||||
| expNeg = exp < 0; | expNeg = exp < 0; | ||||
| if (expNeg) exp = -exp; | |||||
| if (expNeg) { | |||||
| exp = -exp; | |||||
| } | |||||
| } | } | ||||
| ptr = fmtDec((uint16_t)exp, ptr); | ptr = fmtDec((uint16_t)exp, ptr); | ||||
| if (exp < 10) *--ptr = '0'; | |||||
| if (exp < 10) { | |||||
| *--ptr = '0'; | |||||
| } | |||||
| *--ptr = expNeg ? '-' : '+'; | *--ptr = expNeg ? '-' : '+'; | ||||
| *--ptr = expChar; | *--ptr = expChar; | ||||
| } else { | } else { | ||||
| char* tmp = ptr - prec; | char* tmp = ptr - prec; | ||||
| uint32_t fraction = scale10(value - whole, prec); | uint32_t fraction = scale10(value - whole, prec); | ||||
| ptr = fmtDec(fraction, ptr); | ptr = fmtDec(fraction, ptr); | ||||
| while (ptr > tmp) *--ptr = '0'; | |||||
| while (ptr > tmp) { | |||||
| *--ptr = '0'; | |||||
| } | |||||
| *--ptr = '.'; | *--ptr = '.'; | ||||
| } | } | ||||
| ptr = fmtDec(whole, ptr); | ptr = fmtDec(whole, ptr); | ||||
| if (neg) *--ptr = '-'; | |||||
| if (neg) { | |||||
| *--ptr = '-'; | |||||
| } | |||||
| return ptr; | return ptr; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| float v; | float v; | ||||
| const char* successPtr; | const char* successPtr; | ||||
| if (ptr) *ptr = const_cast<char*>(str); | |||||
| if (ptr) { | |||||
| *ptr = const_cast<char*>(str); | |||||
| } | |||||
| while (isspace((c = *str++))) {} | |||||
| while (isSpace((c = *str++))) {} | |||||
| neg = c == '-'; | neg = c == '-'; | ||||
| if (c == '-' || c == '+') c = *str++; | |||||
| if (c == '-' || c == '+') { | |||||
| c = *str++; | |||||
| } | |||||
| // Skip leading zeros | // Skip leading zeros | ||||
| while (c == '0') { | while (c == '0') { | ||||
| c = *str++; | c = *str++; | ||||
| digit = true; | digit = true; | ||||
| } | } | ||||
| for (;;) { | for (;;) { | ||||
| if (isdigit(c)) { | |||||
| if (isDigit(c)) { | |||||
| digit = true; | digit = true; | ||||
| if (nd < 9) { | if (nd < 9) { | ||||
| fract = 10*fract + c - '0'; | fract = 10*fract + c - '0'; | ||||
| nd++; | nd++; | ||||
| if (dot) fracExp--; | |||||
| if (dot) { | |||||
| fracExp--; | |||||
| } | |||||
| } else { | } else { | ||||
| if (!dot) fracExp++; | |||||
| if (!dot) { | |||||
| fracExp++; | |||||
| } | |||||
| } | } | ||||
| } else if (c == '.') { | } else if (c == '.') { | ||||
| if (dot) goto fail; | |||||
| if (dot) { | |||||
| goto fail; | |||||
| } | |||||
| dot = true; | dot = true; | ||||
| } else { | } else { | ||||
| if (!digit) goto fail; | |||||
| if (!digit) { | |||||
| goto fail; | |||||
| } | |||||
| break; | break; | ||||
| } | } | ||||
| successPtr = str; | successPtr = str; | ||||
| if (c == '-' || c == '+') { | if (c == '-' || c == '+') { | ||||
| c = *str++; | c = *str++; | ||||
| } | } | ||||
| while (isdigit(c)) { | |||||
| if (exp > EXP_LIMIT) goto fail; | |||||
| while (isDigit(c)) { | |||||
| if (exp > EXP_LIMIT) { | |||||
| goto fail; | |||||
| } | |||||
| exp = 10*exp + c - '0'; | exp = 10*exp + c - '0'; | ||||
| successPtr = str; | successPtr = str; | ||||
| c = *str++; | c = *str++; | ||||
| } | } | ||||
| fracExp += expNeg ? -exp : exp; | fracExp += expNeg ? -exp : exp; | ||||
| } | } | ||||
| if (ptr) *ptr = const_cast<char*>(successPtr); | |||||
| if (ptr) { | |||||
| *ptr = const_cast<char*>(successPtr); | |||||
| } | |||||
| v = scale10(static_cast<float>(fract), fracExp); | v = scale10(static_cast<float>(fract), fracExp); | ||||
| return neg ? -v: v; | return neg ? -v: v; | ||||
| fail: | |||||
| fail: | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| */ | */ | ||||
| #ifndef FmtNumber_h | #ifndef FmtNumber_h | ||||
| #define FmtNumber_h | #define FmtNumber_h | ||||
| #include <ctype.h> | |||||
| // #include <ctype.h> | |||||
| inline bool isDigit(char c) { | |||||
| return '0' <= c && c <= '9'; | |||||
| } | |||||
| inline bool isSpace(char c) { | |||||
| return c == ' ' || (0X9 <= c && c <= 0XD); | |||||
| } | |||||
| #include <math.h> | #include <math.h> | ||||
| #include <stdint.h> | #include <stdint.h> | ||||
| char* fmtDec(uint16_t n, char* p); | char* fmtDec(uint16_t n, char* p); |
| * <http://www.gnu.org/licenses/>. | * <http://www.gnu.org/licenses/>. | ||||
| */ | */ | ||||
| /** | /** | ||||
| * @file | |||||
| * @file | |||||
| * @brief Software SPI. | * @brief Software SPI. | ||||
| * | * | ||||
| * @defgroup softSPI Software SPI | * @defgroup softSPI Software SPI | ||||
| private: | private: | ||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| inline __attribute__((always_inline)) | inline __attribute__((always_inline)) | ||||
| bool MODE_CPHA(uint8_t mode) {return (mode & 1) != 0;} | |||||
| bool MODE_CPHA(uint8_t mode) { | |||||
| return (mode & 1) != 0; | |||||
| } | |||||
| inline __attribute__((always_inline)) | inline __attribute__((always_inline)) | ||||
| bool MODE_CPOL(uint8_t mode) {return (mode & 2) != 0;} | |||||
| bool MODE_CPOL(uint8_t mode) { | |||||
| return (mode & 2) != 0; | |||||
| } | |||||
| inline __attribute__((always_inline)) | inline __attribute__((always_inline)) | ||||
| void receiveBit(uint8_t bit, uint8_t* data) { | void receiveBit(uint8_t bit, uint8_t* data) { | ||||
| if (MODE_CPHA(Mode)) { | if (MODE_CPHA(Mode)) { | ||||
| nop; | nop; | ||||
| nop; | nop; | ||||
| fastDigitalWrite(SckPin, | fastDigitalWrite(SckPin, | ||||
| MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); | |||||
| if (fastDigitalRead(MisoPin)) *data |= 1 << bit; | |||||
| MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); | |||||
| if (fastDigitalRead(MisoPin)) { | |||||
| *data |= 1 << bit; | |||||
| } | |||||
| if (!MODE_CPHA(Mode)) { | if (!MODE_CPHA(Mode)) { | ||||
| fastDigitalWrite(SckPin, MODE_CPOL(Mode)); | fastDigitalWrite(SckPin, MODE_CPOL(Mode)); | ||||
| } | } | ||||
| } | } | ||||
| fastDigitalWrite(MosiPin, data & (1 << bit)); | fastDigitalWrite(MosiPin, data & (1 << bit)); | ||||
| fastDigitalWrite(SckPin, | fastDigitalWrite(SckPin, | ||||
| MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); | |||||
| MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); | |||||
| nop; | nop; | ||||
| nop; | nop; | ||||
| if (!MODE_CPHA(Mode)) { | if (!MODE_CPHA(Mode)) { | ||||
| } | } | ||||
| fastDigitalWrite(MosiPin, txData & (1 << bit)); | fastDigitalWrite(MosiPin, txData & (1 << bit)); | ||||
| fastDigitalWrite(SckPin, | fastDigitalWrite(SckPin, | ||||
| MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); | |||||
| if (fastDigitalRead(MisoPin)) *rxData |= 1 << bit; | |||||
| MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); | |||||
| if (fastDigitalRead(MisoPin)) { | |||||
| *rxData |= 1 << bit; | |||||
| } | |||||
| if (!MODE_CPHA(Mode)) { | if (!MODE_CPHA(Mode)) { | ||||
| fastDigitalWrite(SckPin, MODE_CPOL(Mode)); | fastDigitalWrite(SckPin, MODE_CPOL(Mode)); | ||||
| } | } |
| return EOF; | return EOF; | ||||
| } | } | ||||
| if (m_flags & F_SWR) { | if (m_flags & F_SWR) { | ||||
| if (!flushBuf()) rtn = EOF; | |||||
| if (!flushBuf()) { | |||||
| rtn = EOF; | |||||
| } | |||||
| } | |||||
| if (!FatFile::close()) { | |||||
| rtn = EOF; | |||||
| } | } | ||||
| if (!FatFile::close()) rtn = EOF; | |||||
| m_r = 0; | m_r = 0; | ||||
| m_w = 0; | m_w = 0; | ||||
| m_flags = 0; | m_flags = 0; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| int StdioStream::fflush() { | int StdioStream::fflush() { | ||||
| if ((m_flags & (F_SWR | F_SRW)) && !(m_flags & F_SRD)) { | if ((m_flags & (F_SWR | F_SRW)) && !(m_flags & F_SRD)) { | ||||
| if (flushBuf() && FatFile::sync()) return 0; | |||||
| if (flushBuf() && FatFile::sync()) { | |||||
| return 0; | |||||
| } | |||||
| } | } | ||||
| return EOF; | return EOF; | ||||
| } | } | ||||
| char* StdioStream::fgets(char* str, size_t num, size_t* len) { | char* StdioStream::fgets(char* str, size_t num, size_t* len) { | ||||
| char* s = str; | char* s = str; | ||||
| size_t n; | size_t n; | ||||
| if (num-- == 0) return 0; | |||||
| if (num-- == 0) { | |||||
| return 0; | |||||
| } | |||||
| while (num) { | while (num) { | ||||
| if ((n = m_r) == 0) { | if ((n = m_r) == 0) { | ||||
| if (!fillBuf()) { | if (!fillBuf()) { | ||||
| if (s == str) return 0; | |||||
| if (s == str) { | |||||
| return 0; | |||||
| } | |||||
| break; | break; | ||||
| } | } | ||||
| n = m_r; | n = m_r; | ||||
| } | } | ||||
| if (n > num) n = num; | |||||
| if (n > num) { | |||||
| n = num; | |||||
| } | |||||
| uint8_t* end = reinterpret_cast<uint8_t*>(memchr(m_p, '\n', n)); | uint8_t* end = reinterpret_cast<uint8_t*>(memchr(m_p, '\n', n)); | ||||
| if (end != 0) { | if (end != 0) { | ||||
| n = ++end - m_p; | n = ++end - m_p; | ||||
| num -= n; | num -= n; | ||||
| } | } | ||||
| *s = 0; | *s = 0; | ||||
| if (len) *len = s - str; | |||||
| if (len) { | |||||
| *len = s - str; | |||||
| } | |||||
| return str; | return str; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| goto fail; | goto fail; | ||||
| } | } | ||||
| } | } | ||||
| if ((oflag & O_EXCL) && !(oflag & O_WRITE)) goto fail; | |||||
| if (!FatFile::open(filename, oflag)) goto fail; | |||||
| if ((oflag & O_EXCL) && !(oflag & O_WRITE)) { | |||||
| goto fail; | |||||
| } | |||||
| if (!FatFile::open(filename, oflag)) { | |||||
| goto fail; | |||||
| } | |||||
| m_r = 0; | m_r = 0; | ||||
| m_w = 0; | m_w = 0; | ||||
| m_p = m_buf; | m_p = m_buf; | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| m_flags = 0; | m_flags = 0; | ||||
| return false; | return false; | ||||
| } | } | ||||
| int StdioStream::fputs_P(PGM_P str) { | int StdioStream::fputs_P(PGM_P str) { | ||||
| PGM_P bgn = str; | PGM_P bgn = str; | ||||
| for (char c; (c = pgm_read_byte(str)); str++) { | for (char c; (c = pgm_read_byte(str)); str++) { | ||||
| if (putc(c) < 0) return EOF; | |||||
| if (putc(c) < 0) { | |||||
| return EOF; | |||||
| } | |||||
| } | } | ||||
| return str - bgn; | return str - bgn; | ||||
| } | } | ||||
| size_t StdioStream::fread(void* ptr, size_t size, size_t count) { | size_t StdioStream::fread(void* ptr, size_t size, size_t count) { | ||||
| uint8_t* dst = reinterpret_cast<uint8_t*>(ptr); | uint8_t* dst = reinterpret_cast<uint8_t*>(ptr); | ||||
| size_t total = size*count; | size_t total = size*count; | ||||
| if (total == 0) return 0; | |||||
| if (total == 0) { | |||||
| return 0; | |||||
| } | |||||
| size_t need = total; | size_t need = total; | ||||
| while (need > m_r) { | while (need > m_r) { | ||||
| memcpy(dst, m_p, m_r); | memcpy(dst, m_p, m_r); | ||||
| m_p = m_buf; | m_p = m_buf; | ||||
| return 0; | return 0; | ||||
| fail: | |||||
| fail: | |||||
| return EOF; | return EOF; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| int32_t StdioStream::ftell() { | int32_t StdioStream::ftell() { | ||||
| uint32_t pos = FatFile::curPosition(); | uint32_t pos = FatFile::curPosition(); | ||||
| if (m_flags & F_SRD) { | if (m_flags & F_SRD) { | ||||
| if (m_r > pos) return -1L; | |||||
| if (m_r > pos) { | |||||
| return -1L; | |||||
| } | |||||
| pos -= m_r; | pos -= m_r; | ||||
| } else if (m_flags & F_SWR) { | } else if (m_flags & F_SWR) { | ||||
| pos += m_p - m_buf; | pos += m_p - m_buf; | ||||
| #if 0 //////////////////////////////////////////////////////////////////////////////////// | #if 0 //////////////////////////////////////////////////////////////////////////////////// | ||||
| const uint8_t* src = static_cast<const uint8_t*>(ptr); | const uint8_t* src = static_cast<const uint8_t*>(ptr); | ||||
| size_t total = count*size; | size_t total = count*size; | ||||
| if (total == 0) return 0; | |||||
| if (total == 0) { | |||||
| return 0; | |||||
| } | |||||
| size_t todo = total; | size_t todo = total; | ||||
| while (todo > m_w) { | while (todo > m_w) { | ||||
| #endif ////////////////////////////////////////////////////////////////////////////////// | #endif ////////////////////////////////////////////////////////////////////////////////// | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| int StdioStream::write(const void* buf, size_t count) { | |||||
| int StdioStream::write(const void* buf, size_t count) { | |||||
| const uint8_t* src = static_cast<const uint8_t*>(buf); | const uint8_t* src = static_cast<const uint8_t*>(buf); | ||||
| size_t todo = count; | size_t todo = count; | ||||
| m_p += m_w; | m_p += m_w; | ||||
| src += m_w; | src += m_w; | ||||
| todo -= m_w; | todo -= m_w; | ||||
| if (!flushBuf()) return EOF; | |||||
| if (!flushBuf()) { | |||||
| return EOF; | |||||
| } | |||||
| } | } | ||||
| memcpy(m_p, src, todo); | memcpy(m_p, src, todo); | ||||
| m_p += todo; | m_p += todo; | ||||
| const char *p = (const char PROGMEM *)str; | const char *p = (const char PROGMEM *)str; | ||||
| uint8_t c; | uint8_t c; | ||||
| while ((c = pgm_read_byte(p))) { | while ((c = pgm_read_byte(p))) { | ||||
| if (putc(c) < 0) return 0; | |||||
| if (putc(c) < 0) { | |||||
| return 0; | |||||
| } | |||||
| p++; | p++; | ||||
| } | } | ||||
| return p - (const char PROGMEM *)str; | return p - (const char PROGMEM *)str; | ||||
| } | } | ||||
| // check for NaN INF OVF | // check for NaN INF OVF | ||||
| if (isnan(value)) { | if (isnan(value)) { | ||||
| if (fputs_P(PSTR("nan")) < 0) return -1; | |||||
| if (fputs_P(PSTR("nan")) < 0) { | |||||
| return -1; | |||||
| } | |||||
| rtn += 3; | rtn += 3; | ||||
| } else if (isinf(value)) { | } else if (isinf(value)) { | ||||
| if (fputs_P(PSTR("inf")) < 0) return -1; | |||||
| if (fputs_P(PSTR("inf")) < 0) { | |||||
| return -1; | |||||
| } | |||||
| rtn += 3; | rtn += 3; | ||||
| } else if (value > 4294967040.0) { | } else if (value > 4294967040.0) { | ||||
| if (fputs_P(PSTR("ovf")) < 0) return -1;; | |||||
| if (fputs_P(PSTR("ovf")) < 0) { | |||||
| return -1; | |||||
| } | |||||
| rtn += 3; | rtn += 3; | ||||
| } else { | } else { | ||||
| if (sign) { | if (sign) { | ||||
| if (putc(sign) < 0) return -1; | |||||
| if (putc(sign) < 0) { | |||||
| return -1; | |||||
| } | |||||
| rtn++; | rtn++; | ||||
| } | } | ||||
| if (prec > 9) prec = 9; | |||||
| if (prec > 9) { | |||||
| prec = 9; | |||||
| } | |||||
| /* | |||||
| uint32_t s = 1; | |||||
| for (uint8_t i = 0; i < prec; i++) { | |||||
| // s *= 10; | |||||
| s = ((s << 2) + s) << 1; | |||||
| } | |||||
| // round value | |||||
| value += 0.5/s; | |||||
| */ | |||||
| /* | |||||
| uint32_t s = 1; | |||||
| for (uint8_t i = 0; i < prec; i++) { | |||||
| // s *= 10; | |||||
| s = ((s << 2) + s) << 1; | |||||
| } | |||||
| // round value | |||||
| value += 0.5/s; | |||||
| */ | |||||
| value += scale10(0.5, -prec); | value += scale10(0.5, -prec); | ||||
| uint32_t whole = value; | uint32_t whole = value; | ||||
| int np; | int np; | ||||
| if ((np = printDec(whole)) < 0) return -1; | |||||
| if ((np = printDec(whole)) < 0) { | |||||
| return -1; | |||||
| } | |||||
| rtn += np; | rtn += np; | ||||
| if (prec) { | if (prec) { | ||||
| if (putc('.') < 0) return -1; | |||||
| if (putc('.') < 0) { | |||||
| return -1; | |||||
| } | |||||
| char* str = fmtSpace(prec); | char* str = fmtSpace(prec); | ||||
| if (!str) return -1; | |||||
| if (!str) { | |||||
| return -1; | |||||
| } | |||||
| char* tmp = str - prec; | char* tmp = str - prec; | ||||
| // uint32_t fraction = s*(value - whole); | |||||
| // uint32_t fraction = s*(value - whole); | |||||
| uint32_t fraction = scale10(value - whole, prec); | uint32_t fraction = scale10(value - whole, prec); | ||||
| ptr = fmtDec(fraction, str); | ptr = fmtDec(fraction, str); | ||||
| while (ptr > tmp) *--ptr = '0'; | |||||
| while (ptr > tmp) { | |||||
| *--ptr = '0'; | |||||
| } | |||||
| rtn += prec + 1; | rtn += prec + 1; | ||||
| } | } | ||||
| } | } | ||||
| return rtn; | return rtn; | ||||
| #endif | |||||
| #endif | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| int StdioStream::printDec(signed char n) { | int StdioStream::printDec(signed char n) { | ||||
| uint8_t s = 0; | uint8_t s = 0; | ||||
| if (n < 0) { | if (n < 0) { | ||||
| if (fputc('-') < 0) return -1; | |||||
| if (fputc('-') < 0) { | |||||
| return -1; | |||||
| } | |||||
| n = -n; | n = -n; | ||||
| s = 1; | s = 1; | ||||
| } | } | ||||
| int s; | int s; | ||||
| uint8_t rtn = 0; | uint8_t rtn = 0; | ||||
| if (n < 0) { | if (n < 0) { | ||||
| if (fputc('-') < 0) return -1; | |||||
| if (fputc('-') < 0) { | |||||
| return -1; | |||||
| } | |||||
| n = -n; | n = -n; | ||||
| rtn++; | rtn++; | ||||
| } | } | ||||
| if ((s = printDec((uint16_t)n)) < 0) return s; | |||||
| if ((s = printDec((uint16_t)n)) < 0) { | |||||
| return s; | |||||
| } | |||||
| return rtn; | return rtn; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| len = n < 1000 ? 3 : n < 10000 ? 4 : 5; | len = n < 1000 ? 3 : n < 10000 ? 4 : 5; | ||||
| } | } | ||||
| char* str = fmtSpace(len); | char* str = fmtSpace(len); | ||||
| if (!str) return -1; | |||||
| if (!str) { | |||||
| return -1; | |||||
| } | |||||
| fmtDec(n, str); | fmtDec(n, str); | ||||
| return len; | return len; | ||||
| #endif | #endif | ||||
| int StdioStream::printDec(int32_t n) { | int StdioStream::printDec(int32_t n) { | ||||
| uint8_t s = 0; | uint8_t s = 0; | ||||
| if (n < 0) { | if (n < 0) { | ||||
| if (fputc('-') < 0) return -1; | |||||
| if (fputc('-') < 0) { | |||||
| return -1; | |||||
| } | |||||
| n = -n; | n = -n; | ||||
| s = 1; | s = 1; | ||||
| } | } | ||||
| } | } | ||||
| char* str = fmtSpace(len); | char* str = fmtSpace(len); | ||||
| if (!str) return -1; | |||||
| if (!str) { | |||||
| return -1; | |||||
| } | |||||
| fmtDec(n, str); | fmtDec(n, str); | ||||
| return len; | return len; | ||||
| #endif | #endif | ||||
| len = n < 0X100000 ? 5 : n < 0X1000000 ? 6 : n < 0X10000000 ? 7 : 8; | len = n < 0X100000 ? 5 : n < 0X1000000 ? 6 : n < 0X10000000 ? 7 : 8; | ||||
| } | } | ||||
| char* str = fmtSpace(len); | char* str = fmtSpace(len); | ||||
| if (!str) return -1; | |||||
| if (!str) { | |||||
| return -1; | |||||
| } | |||||
| do { | do { | ||||
| uint8_t h = n & 0XF; | uint8_t h = n & 0XF; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| bool StdioStream::rewind() { | bool StdioStream::rewind() { | ||||
| if (m_flags & F_SWR) { | if (m_flags & F_SWR) { | ||||
| if (!flushBuf()) return false; | |||||
| if (!flushBuf()) { | |||||
| return false; | |||||
| } | |||||
| } | } | ||||
| FatFile::seekSet(0); | FatFile::seekSet(0); | ||||
| m_r = 0; | m_r = 0; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| int StdioStream::ungetc(int c) { | int StdioStream::ungetc(int c) { | ||||
| // error if EOF. | // error if EOF. | ||||
| if (c == EOF) return EOF; | |||||
| if (c == EOF) { | |||||
| return EOF; | |||||
| } | |||||
| // error if not reading. | // error if not reading. | ||||
| if ((m_flags & F_SRD) == 0) return EOF; | |||||
| if ((m_flags & F_SRD) == 0) { | |||||
| return EOF; | |||||
| } | |||||
| // error if no space. | // error if no space. | ||||
| if (m_p == m_buf) return EOF; | |||||
| if (m_p == m_buf) { | |||||
| return EOF; | |||||
| } | |||||
| m_r++; | m_r++; | ||||
| m_flags &= ~F_EOF; | m_flags &= ~F_EOF; | ||||
| return *--m_p = (uint8_t)c; | return *--m_p = (uint8_t)c; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // private | // private | ||||
| bool StdioStream::fillBuf() { | bool StdioStream::fillBuf() { | ||||
| if (!(m_flags & F_SRD)) { /////////////check for F_ERR and F_EOF ??///////////////// | |||||
| if (!(m_flags & | |||||
| F_SRD)) { /////////////check for F_ERR and F_EOF ??///////////////// | |||||
| if (!(m_flags & F_SRW)) { | if (!(m_flags & F_SRW)) { | ||||
| m_flags |= F_ERR; | m_flags |= F_ERR; | ||||
| return false; | return false; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| // private | // private | ||||
| bool StdioStream::flushBuf() { | bool StdioStream::flushBuf() { | ||||
| if (!(m_flags & F_SWR)) { /////////////////check for F_ERR ??//////////////////////// | |||||
| if (!(m_flags & | |||||
| F_SWR)) { /////////////////check for F_ERR ??//////////////////////// | |||||
| if (!(m_flags & F_SRW)) { | if (!(m_flags & F_SRW)) { | ||||
| m_flags |= F_ERR; | m_flags |= F_ERR; | ||||
| return false; | return false; | ||||
| uint8_t n = m_p - m_buf; | uint8_t n = m_p - m_buf; | ||||
| m_p = m_buf; | m_p = m_buf; | ||||
| m_w = sizeof(m_buf); | m_w = sizeof(m_buf); | ||||
| if (FatFile::write(m_buf, n) == n) return true; | |||||
| if (FatFile::write(m_buf, n) == n) { | |||||
| return true; | |||||
| } | |||||
| m_flags |= F_ERR; | m_flags |= F_ERR; | ||||
| return false; | return false; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| int StdioStream::flushPut(uint8_t c) { | int StdioStream::flushPut(uint8_t c) { | ||||
| if (!flushBuf()) return EOF; | |||||
| if (!flushBuf()) { | |||||
| return EOF; | |||||
| } | |||||
| m_w--; | m_w--; | ||||
| return *m_p++ = c; | return *m_p++ = c; | ||||
| } | } | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| } | } | ||||
| if (len > m_w) return 0; | |||||
| if (len > m_w) { | |||||
| return 0; | |||||
| } | |||||
| m_p += len; | m_p += len; | ||||
| m_w -= len; | m_w -= len; | ||||
| return reinterpret_cast<char*>(m_p); | return reinterpret_cast<char*>(m_p); |
| */ | */ | ||||
| class StdioStream : private FatFile { | class StdioStream : private FatFile { | ||||
| public: | public: | ||||
| /** Constructor | |||||
| * | |||||
| */ | |||||
| /** Constructor | |||||
| * | |||||
| */ | |||||
| StdioStream() { | StdioStream() { | ||||
| m_w = m_r = 0; | m_w = m_r = 0; | ||||
| m_p = m_buf; | m_p = m_buf; | ||||
| } | } | ||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** Clear the stream's end-of-file and error indicators. */ | /** Clear the stream's end-of-file and error indicators. */ | ||||
| void clearerr() {m_flags &= ~(F_ERR | F_EOF);} | |||||
| void clearerr() { | |||||
| m_flags &= ~(F_ERR | F_EOF); | |||||
| } | |||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** Close a stream. | /** Close a stream. | ||||
| * | * | ||||
| /** Test the stream's end-of-file indicator. | /** Test the stream's end-of-file indicator. | ||||
| * \return non-zero if and only if the end-of-file indicator is set. | * \return non-zero if and only if the end-of-file indicator is set. | ||||
| */ | */ | ||||
| int feof() {return (m_flags & F_EOF) != 0;} | |||||
| int feof() { | |||||
| return (m_flags & F_EOF) != 0; | |||||
| } | |||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** Test the stream's error indicator. | /** Test the stream's error indicator. | ||||
| * \return return non-zero if and only if the error indicator is set. | * \return return non-zero if and only if the error indicator is set. | ||||
| */ | */ | ||||
| int ferror() {return (m_flags & F_ERR) != 0;} | |||||
| int ferror() { | |||||
| return (m_flags & F_ERR) != 0; | |||||
| } | |||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** Flush the stream. | /** Flush the stream. | ||||
| * | * | ||||
| * set and the fgetc function returns EOF. Otherwise, the fgetc function | * set and the fgetc function returns EOF. Otherwise, the fgetc function | ||||
| * returns the next character from the input stream. | * returns the next character from the input stream. | ||||
| */ | */ | ||||
| int fgetc() {return m_r-- == 0 ? fillGet() : *m_p++;} | |||||
| int fgetc() { | |||||
| return m_r-- == 0 ? fillGet() : *m_p++; | |||||
| } | |||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** Get a string from a stream. | /** Get a string from a stream. | ||||
| * | * | ||||
| * has written. Otherwise, it returns EOF and sets the error indicator for | * has written. Otherwise, it returns EOF and sets the error indicator for | ||||
| * the stream. | * the stream. | ||||
| */ | */ | ||||
| int fputc(int c) {return m_w-- == 0 ? flushPut(c) : *m_p++ = c;} | |||||
| int fputc(int c) { | |||||
| return m_w-- == 0 ? flushPut(c) : *m_p++ = c; | |||||
| } | |||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** Write a string to a stream. | /** Write a string to a stream. | ||||
| * | * | ||||
| * returns the next character from the input stream. | * returns the next character from the input stream. | ||||
| */ | */ | ||||
| inline __attribute__((always_inline)) | inline __attribute__((always_inline)) | ||||
| int getc() {return m_r-- == 0 ? fillGet() : *m_p++;} | |||||
| int getc() { | |||||
| return m_r-- == 0 ? fillGet() : *m_p++; | |||||
| } | |||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** Write a byte to a stream. | /** Write a byte to a stream. | ||||
| * | * | ||||
| * the stream. | * the stream. | ||||
| */ | */ | ||||
| inline __attribute__((always_inline)) | inline __attribute__((always_inline)) | ||||
| int putc(int c) {return m_w-- == 0 ? flushPut(c) : *m_p++ = c;} | |||||
| int putc(int c) { | |||||
| return m_w-- == 0 ? flushPut(c) : *m_p++ = c; | |||||
| } | |||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** Write a CR/LF. | /** Write a CR/LF. | ||||
| * | * | ||||
| inline __attribute__((always_inline)) | inline __attribute__((always_inline)) | ||||
| int putCRLF() { | int putCRLF() { | ||||
| if (m_w < 2) { | if (m_w < 2) { | ||||
| if (!flushBuf()) return -1; | |||||
| if (!flushBuf()) { | |||||
| return -1; | |||||
| } | |||||
| } | } | ||||
| *m_p++ = '\r'; | *m_p++ = '\r'; | ||||
| *m_p++ = '\n'; | *m_p++ = '\n'; | ||||
| */ | */ | ||||
| int printDec(float value, uint8_t prec); | int printDec(float value, uint8_t prec); | ||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** Print a number followed by a field terminator. | |||||
| * \param[in] value The number to be printed. | |||||
| * \param[in] term The field terminator. | |||||
| * \param[in] prec Number of digits after decimal point. | |||||
| * \return The number of bytes written or -1 if an error occurs. | |||||
| */ | |||||
| /** Print a number followed by a field terminator. | |||||
| * \param[in] value The number to be printed. | |||||
| * \param[in] term The field terminator. | |||||
| * \param[in] prec Number of digits after decimal point. | |||||
| * \return The number of bytes written or -1 if an error occurs. | |||||
| */ | |||||
| int printField(double value, char term, uint8_t prec = 2) { | int printField(double value, char term, uint8_t prec = 2) { | ||||
| return printField(static_cast<float>(value), term, prec) > 0; | return printField(static_cast<float>(value), term, prec) > 0; | ||||
| } | } | ||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** Print a number followed by a field terminator. | |||||
| * \param[in] value The number to be printed. | |||||
| * \param[in] term The field terminator. | |||||
| * \param[in] prec Number of digits after decimal point. | |||||
| * \return The number of bytes written or -1 if an error occurs. | |||||
| */ | |||||
| /** Print a number followed by a field terminator. | |||||
| * \param[in] value The number to be printed. | |||||
| * \param[in] term The field terminator. | |||||
| * \param[in] prec Number of digits after decimal point. | |||||
| * \return The number of bytes written or -1 if an error occurs. | |||||
| */ | |||||
| int printField(float value, char term, uint8_t prec = 2) { | int printField(float value, char term, uint8_t prec = 2) { | ||||
| int rtn = printDec(value, prec); | int rtn = printDec(value, prec); | ||||
| return rtn < 0 || putc(term) < 0 ? -1 : rtn + 1; | return rtn < 0 || putc(term) < 0 ? -1 : rtn + 1; | ||||
| } | } | ||||
| //---------------------------------------------------------------------------- | //---------------------------------------------------------------------------- | ||||
| /** Print a number followed by a field terminator. | |||||
| * \param[in] value The number to be printed. | |||||
| * \param[in] term The field terminator. | |||||
| * \return The number of bytes written or -1 if an error occurs. | |||||
| */ | |||||
| /** Print a number followed by a field terminator. | |||||
| * \param[in] value The number to be printed. | |||||
| * \param[in] term The field terminator. | |||||
| * \return The number of bytes written or -1 if an error occurs. | |||||
| */ | |||||
| template <typename T> | template <typename T> | ||||
| int printField(T value, char term) { | int printField(T value, char term) { | ||||
| int rtn = printDec(value); | int rtn = printDec(value); |
| * Warning: The string will not be copied so must stay in scope. | * Warning: The string will not be copied so must stay in scope. | ||||
| */ | */ | ||||
| explicit ibufstream(const char* str) { | explicit ibufstream(const char* str) { | ||||
| init(str); | |||||
| init(str); | |||||
| } | } | ||||
| /** Initialize an ibufstream | /** Initialize an ibufstream | ||||
| * \param[in] str pointer to string to be parsed | * \param[in] str pointer to string to be parsed | ||||
| protected: | protected: | ||||
| /// @cond SHOW_PROTECTED | /// @cond SHOW_PROTECTED | ||||
| int16_t getch() { | int16_t getch() { | ||||
| if (m_pos < m_len) return m_buf[m_pos++]; | |||||
| if (m_pos < m_len) { | |||||
| return m_buf[m_pos++]; | |||||
| } | |||||
| setstate(eofbit); | setstate(eofbit); | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| void getpos(FatPos_t *pos) { | void getpos(FatPos_t *pos) { | ||||
| pos->position = m_pos; | pos->position = m_pos; | ||||
| } | } | ||||
| bool seekoff(off_type off, seekdir way) {return false;} | |||||
| bool seekoff(off_type off, seekdir way) { | |||||
| return false; | |||||
| } | |||||
| bool seekpos(pos_type pos) { | bool seekpos(pos_type pos) { | ||||
| if (pos < m_len) { | if (pos < m_len) { | ||||
| m_pos = pos; | m_pos = pos; | ||||
| m_in = 0; | m_in = 0; | ||||
| } | } | ||||
| /** \return a pointer to the buffer */ | /** \return a pointer to the buffer */ | ||||
| char* buf() {return m_buf;} | |||||
| char* buf() { | |||||
| return m_buf; | |||||
| } | |||||
| /** \return the length of the formatted string */ | /** \return the length of the formatted string */ | ||||
| size_t length() {return m_in;} | |||||
| size_t length() { | |||||
| return m_in; | |||||
| } | |||||
| protected: | protected: | ||||
| /// @cond SHOW_PROTECTED | /// @cond SHOW_PROTECTED | ||||
| m_buf[m_in]= '\0'; | m_buf[m_in]= '\0'; | ||||
| } | } | ||||
| void putstr(const char *str) { | void putstr(const char *str) { | ||||
| while (*str) putch(*str++); | |||||
| while (*str) { | |||||
| putch(*str++); | |||||
| } | |||||
| } | |||||
| bool seekoff(off_type off, seekdir way) { | |||||
| return false; | |||||
| } | } | ||||
| bool seekoff(off_type off, seekdir way) {return false;} | |||||
| bool seekpos(pos_type pos) { | bool seekpos(pos_type pos) { | ||||
| if (pos > m_in) return false; | |||||
| if (pos > m_in) { | |||||
| return false; | |||||
| } | |||||
| m_in = pos; | m_in = pos; | ||||
| m_buf[m_in] = '\0'; | m_buf[m_in] = '\0'; | ||||
| return true; | return true; | ||||
| } | } | ||||
| bool sync() {return true;} | |||||
| bool sync() { | |||||
| return true; | |||||
| } | |||||
| pos_type tellpos() { | pos_type tellpos() { | ||||
| return m_in; | return m_in; |
| */ | */ | ||||
| #include "fstream.h" | #include "fstream.h" | ||||
| //============================================================================== | //============================================================================== | ||||
| /// @cond SHOW_PROTECTED | |||||
| /// @cond SHOW_PROTECTED | |||||
| int16_t FatStreamBase::getch() { | int16_t FatStreamBase::getch() { | ||||
| uint8_t c; | uint8_t c; | ||||
| int8_t s = read(&c, 1); | int8_t s = read(&c, 1); | ||||
| } | } | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| if (c != '\r' || (getmode() & ios::binary)) return c; | |||||
| if (c != '\r' || (getmode() & ios::binary)) { | |||||
| return c; | |||||
| } | |||||
| s = read(&c, 1); | s = read(&c, 1); | ||||
| if (s == 1 && c == '\n') return c; | |||||
| if (s == 1) seekCur(-1); | |||||
| if (s == 1 && c == '\n') { | |||||
| return c; | |||||
| } | |||||
| if (s == 1) { | |||||
| seekCur(-1); | |||||
| } | |||||
| return '\r'; | return '\r'; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void FatStreamBase::open(const char* path, ios::openmode mode) { | void FatStreamBase::open(const char* path, ios::openmode mode) { | ||||
| uint8_t flags; | |||||
| uint8_t flags; | |||||
| switch (mode & (app | in | out | trunc)) { | switch (mode & (app | in | out | trunc)) { | ||||
| case app | in: | |||||
| case app | in | out: | |||||
| flags = O_RDWR | O_APPEND | O_CREAT; | |||||
| break; | |||||
| case app | in: | |||||
| case app | in | out: | |||||
| flags = O_RDWR | O_APPEND | O_CREAT; | |||||
| break; | |||||
| case app: | |||||
| case app | out: | |||||
| flags = O_WRITE | O_APPEND | O_CREAT; | |||||
| break; | |||||
| case app: | |||||
| case app | out: | |||||
| flags = O_WRITE | O_APPEND | O_CREAT; | |||||
| break; | |||||
| case in: | |||||
| flags = O_READ; | |||||
| break; | |||||
| case in: | |||||
| flags = O_READ; | |||||
| break; | |||||
| case in | out: | |||||
| flags = O_RDWR; | |||||
| break; | |||||
| case in | out: | |||||
| flags = O_RDWR; | |||||
| break; | |||||
| case in | out | trunc: | |||||
| flags = O_RDWR | O_TRUNC | O_CREAT; | |||||
| break; | |||||
| case in | out | trunc: | |||||
| flags = O_RDWR | O_TRUNC | O_CREAT; | |||||
| break; | |||||
| case out: | |||||
| case out | trunc: | |||||
| flags = O_WRITE | O_TRUNC | O_CREAT; | |||||
| break; | |||||
| case out: | |||||
| case out | trunc: | |||||
| flags = O_WRITE | O_TRUNC | O_CREAT; | |||||
| break; | |||||
| default: | |||||
| goto fail; | |||||
| default: | |||||
| goto fail; | |||||
| } | |||||
| if (mode & ios::ate) { | |||||
| flags |= O_AT_END; | |||||
| } | |||||
| if (!FatFile::open(path, flags)) { | |||||
| goto fail; | |||||
| } | } | ||||
| if (mode & ios::ate) flags |= O_AT_END; | |||||
| if (!FatFile::open(path, flags)) goto fail; | |||||
| setmode(mode); | setmode(mode); | ||||
| clear(); | clear(); | ||||
| return; | return; | ||||
| fail: | |||||
| fail: | |||||
| FatFile::close(); | FatFile::close(); | ||||
| setstate(failbit); | setstate(failbit); | ||||
| return; | return; | ||||
| write('\r'); | write('\r'); | ||||
| } | } | ||||
| write(c); | write(c); | ||||
| if (getWriteError()) setstate(badbit); | |||||
| if (getWriteError()) { | |||||
| setstate(badbit); | |||||
| } | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void FatStreamBase::putstr(const char* str) { | void FatStreamBase::putstr(const char* str) { | ||||
| while (1) { | while (1) { | ||||
| char c = str[n]; | char c = str[n]; | ||||
| if (c == '\0' || (c == '\n' && !(getmode() & ios::binary))) { | if (c == '\0' || (c == '\n' && !(getmode() & ios::binary))) { | ||||
| if (n > 0) write(str, n); | |||||
| if (c == '\0') break; | |||||
| if (n > 0) { | |||||
| write(str, n); | |||||
| } | |||||
| if (c == '\0') { | |||||
| break; | |||||
| } | |||||
| write('\r'); | write('\r'); | ||||
| str += n; | str += n; | ||||
| n = 0; | n = 0; | ||||
| } | } | ||||
| n++; | n++; | ||||
| } | } | ||||
| if (getWriteError()) setstate(badbit); | |||||
| if (getWriteError()) { | |||||
| setstate(badbit); | |||||
| } | |||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| /** Internal do not use | /** Internal do not use | ||||
| bool FatStreamBase::seekoff(off_type off, seekdir way) { | bool FatStreamBase::seekoff(off_type off, seekdir way) { | ||||
| pos_type pos; | pos_type pos; | ||||
| switch (way) { | switch (way) { | ||||
| case beg: | |||||
| pos = off; | |||||
| break; | |||||
| case beg: | |||||
| pos = off; | |||||
| break; | |||||
| case cur: | |||||
| pos = curPosition() + off; | |||||
| break; | |||||
| case cur: | |||||
| pos = curPosition() + off; | |||||
| break; | |||||
| case end: | |||||
| pos = fileSize() + off; | |||||
| break; | |||||
| case end: | |||||
| pos = fileSize() + off; | |||||
| break; | |||||
| default: | |||||
| return false; | |||||
| default: | |||||
| return false; | |||||
| } | } | ||||
| return seekpos(pos); | return seekpos(pos); | ||||
| } | } |
| /** Internal do not use | /** Internal do not use | ||||
| * \return mode | * \return mode | ||||
| */ | */ | ||||
| ios::openmode getmode() {return m_mode;} | |||||
| ios::openmode getmode() { | |||||
| return m_mode; | |||||
| } | |||||
| /** Internal do not use | /** Internal do not use | ||||
| * \param[in] mode | * \param[in] mode | ||||
| */ | */ | ||||
| void setmode(ios::openmode mode) {m_mode = mode;} | |||||
| void setmode(ios::openmode mode) { | |||||
| m_mode = mode; | |||||
| } | |||||
| bool seekoff(off_type off, seekdir way); | bool seekoff(off_type off, seekdir way); | ||||
| bool seekpos(pos_type pos); | bool seekpos(pos_type pos); | ||||
| int write(const void* buf, size_t n); | int write(const void* buf, size_t n); | ||||
| /** Close a file and force cached data and directory information | /** Close a file and force cached data and directory information | ||||
| * to be written to the storage device. | * to be written to the storage device. | ||||
| */ | */ | ||||
| void close() {FatFile::close();} | |||||
| void close() { | |||||
| FatFile::close(); | |||||
| } | |||||
| /** Open a fstream | /** Open a fstream | ||||
| * \param[in] path file to open | * \param[in] path file to open | ||||
| * \param[in] mode open mode | * \param[in] mode open mode | ||||
| FatStreamBase::open(path, mode); | FatStreamBase::open(path, mode); | ||||
| } | } | ||||
| /** \return True if stream is open else false. */ | /** \return True if stream is open else false. */ | ||||
| bool is_open () {return FatFile::isOpen();} | |||||
| bool is_open() { | |||||
| return FatFile::isOpen(); | |||||
| } | |||||
| protected: | protected: | ||||
| /// @cond SHOW_PROTECTED | /// @cond SHOW_PROTECTED | ||||
| /** Internal - do not use | /** Internal - do not use | ||||
| * \return | * \return | ||||
| */ | */ | ||||
| int16_t getch() {return FatStreamBase::getch();} | |||||
| /** Internal - do not use | |||||
| * \param[out] pos | |||||
| */ | |||||
| void getpos(FatPos_t* pos) {FatFile::getpos(pos);} | |||||
| int16_t getch() { | |||||
| return FatStreamBase::getch(); | |||||
| } | |||||
| /** Internal - do not use | |||||
| * \param[out] pos | |||||
| */ | |||||
| void getpos(FatPos_t* pos) { | |||||
| FatFile::getpos(pos); | |||||
| } | |||||
| /** Internal - do not use | /** Internal - do not use | ||||
| * \param[in] c | * \param[in] c | ||||
| */ | */ | ||||
| void putch(char c) {FatStreamBase::putch(c);} | |||||
| void putch(char c) { | |||||
| FatStreamBase::putch(c); | |||||
| } | |||||
| /** Internal - do not use | /** Internal - do not use | ||||
| * \param[in] str | * \param[in] str | ||||
| */ | */ | ||||
| void putstr(const char *str) {FatStreamBase::putstr(str);} | |||||
| void putstr(const char *str) { | |||||
| FatStreamBase::putstr(str); | |||||
| } | |||||
| /** Internal - do not use | /** Internal - do not use | ||||
| * \param[in] pos | * \param[in] pos | ||||
| */ | */ | ||||
| bool seekoff(off_type off, seekdir way) { | bool seekoff(off_type off, seekdir way) { | ||||
| return FatStreamBase::seekoff(off, way); | return FatStreamBase::seekoff(off, way); | ||||
| } | } | ||||
| bool seekpos(pos_type pos) {return FatStreamBase::seekpos(pos);} | |||||
| void setpos(FatPos_t* pos) {FatFile::setpos(pos);} | |||||
| bool sync() {return FatStreamBase::sync();} | |||||
| pos_type tellpos() {return FatStreamBase::curPosition();} | |||||
| bool seekpos(pos_type pos) { | |||||
| return FatStreamBase::seekpos(pos); | |||||
| } | |||||
| void setpos(FatPos_t* pos) { | |||||
| FatFile::setpos(pos); | |||||
| } | |||||
| bool sync() { | |||||
| return FatStreamBase::sync(); | |||||
| } | |||||
| pos_type tellpos() { | |||||
| return FatStreamBase::curPosition(); | |||||
| } | |||||
| /// @endcond | /// @endcond | ||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| /** Close a file and force cached data and directory information | /** Close a file and force cached data and directory information | ||||
| * to be written to the storage device. | * to be written to the storage device. | ||||
| */ | */ | ||||
| void close() {FatFile::close();} | |||||
| void close() { | |||||
| FatFile::close(); | |||||
| } | |||||
| /** \return True if stream is open else false. */ | /** \return True if stream is open else false. */ | ||||
| bool is_open() {return FatFile::isOpen();} | |||||
| bool is_open() { | |||||
| return FatFile::isOpen(); | |||||
| } | |||||
| /** Open an ifstream | /** Open an ifstream | ||||
| * \param[in] path file to open | * \param[in] path file to open | ||||
| * \param[in] mode open mode | * \param[in] mode open mode | ||||
| /** Internal - do not use | /** Internal - do not use | ||||
| * \return | * \return | ||||
| */ | */ | ||||
| int16_t getch() {return FatStreamBase::getch();} | |||||
| int16_t getch() { | |||||
| return FatStreamBase::getch(); | |||||
| } | |||||
| /** Internal - do not use | /** Internal - do not use | ||||
| * \param[out] pos | * \param[out] pos | ||||
| */ | */ | ||||
| void getpos(FatPos_t* pos) {FatFile::getpos(pos);} | |||||
| void getpos(FatPos_t* pos) { | |||||
| FatFile::getpos(pos); | |||||
| } | |||||
| /** Internal - do not use | /** Internal - do not use | ||||
| * \param[in] pos | * \param[in] pos | ||||
| */ | */ | ||||
| bool seekoff(off_type off, seekdir way) { | bool seekoff(off_type off, seekdir way) { | ||||
| return FatStreamBase::seekoff(off, way); | return FatStreamBase::seekoff(off, way); | ||||
| } | } | ||||
| bool seekpos(pos_type pos) {return FatStreamBase::seekpos(pos);} | |||||
| void setpos(FatPos_t* pos) {FatFile::setpos(pos);} | |||||
| pos_type tellpos() {return FatStreamBase::curPosition();} | |||||
| bool seekpos(pos_type pos) { | |||||
| return FatStreamBase::seekpos(pos); | |||||
| } | |||||
| void setpos(FatPos_t* pos) { | |||||
| FatFile::setpos(pos); | |||||
| } | |||||
| pos_type tellpos() { | |||||
| return FatStreamBase::curPosition(); | |||||
| } | |||||
| /// @endcond | /// @endcond | ||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| /** Close a file and force cached data and directory information | /** Close a file and force cached data and directory information | ||||
| * to be written to the storage device. | * to be written to the storage device. | ||||
| */ | */ | ||||
| void close() {FatFile::close();} | |||||
| void close() { | |||||
| FatFile::close(); | |||||
| } | |||||
| /** Open an ofstream | /** Open an ofstream | ||||
| * \param[in] path file to open | * \param[in] path file to open | ||||
| * \param[in] mode open mode | * \param[in] mode open mode | ||||
| FatStreamBase::open(path, mode | out); | FatStreamBase::open(path, mode | out); | ||||
| } | } | ||||
| /** \return True if stream is open else false. */ | /** \return True if stream is open else false. */ | ||||
| bool is_open() {return FatFile::isOpen();} | |||||
| bool is_open() { | |||||
| return FatFile::isOpen(); | |||||
| } | |||||
| protected: | protected: | ||||
| /// @cond SHOW_PROTECTED | /// @cond SHOW_PROTECTED | ||||
| * Internal do not use | * Internal do not use | ||||
| * \param[in] c | * \param[in] c | ||||
| */ | */ | ||||
| void putch(char c) {FatStreamBase::putch(c);} | |||||
| void putstr(const char* str) {FatStreamBase::putstr(str);} | |||||
| void putch(char c) { | |||||
| FatStreamBase::putch(c); | |||||
| } | |||||
| void putstr(const char* str) { | |||||
| FatStreamBase::putstr(str); | |||||
| } | |||||
| bool seekoff(off_type off, seekdir way) { | bool seekoff(off_type off, seekdir way) { | ||||
| return FatStreamBase::seekoff(off, way); | return FatStreamBase::seekoff(off, way); | ||||
| } | } | ||||
| bool seekpos(pos_type pos) {return FatStreamBase::seekpos(pos);} | |||||
| bool seekpos(pos_type pos) { | |||||
| return FatStreamBase::seekpos(pos); | |||||
| } | |||||
| /** | /** | ||||
| * Internal do not use | * Internal do not use | ||||
| * \param[in] b | * \param[in] b | ||||
| */ | */ | ||||
| bool sync() {return FatStreamBase::sync();} | |||||
| pos_type tellpos() {return FatStreamBase::curPosition();} | |||||
| bool sync() { | |||||
| return FatStreamBase::sync(); | |||||
| } | |||||
| pos_type tellpos() { | |||||
| return FatStreamBase::curPosition(); | |||||
| } | |||||
| /// @endcond | /// @endcond | ||||
| }; | }; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ |
| #define ios_h | #define ios_h | ||||
| #include "FatFile.h" | #include "FatFile.h" | ||||
| /** | /** | ||||
| * \file | |||||
| * \file | |||||
| * \brief \ref ios_base and \ref ios classes | * \brief \ref ios_base and \ref ios classes | ||||
| */ | */ | ||||
| //============================================================================== | //============================================================================== | ||||
| ios_base() : m_fill(' '), m_fmtflags(dec | right | skipws) | ios_base() : m_fill(' '), m_fmtflags(dec | right | skipws) | ||||
| , m_precision(2), m_width(0) {} | , m_precision(2), m_width(0) {} | ||||
| /** \return fill character */ | /** \return fill character */ | ||||
| char fill() {return m_fill;} | |||||
| char fill() { | |||||
| return m_fill; | |||||
| } | |||||
| /** Set fill character | /** Set fill character | ||||
| * \param[in] c new fill character | * \param[in] c new fill character | ||||
| * \return old fill character | * \return old fill character | ||||
| return r; | return r; | ||||
| } | } | ||||
| /** \return format flags */ | /** \return format flags */ | ||||
| fmtflags flags() const {return m_fmtflags;} | |||||
| fmtflags flags() const { | |||||
| return m_fmtflags; | |||||
| } | |||||
| /** set format flags | /** set format flags | ||||
| * \param[in] fl new flag | * \param[in] fl new flag | ||||
| * \return old flags | * \return old flags | ||||
| return tmp; | return tmp; | ||||
| } | } | ||||
| /** \return precision */ | /** \return precision */ | ||||
| int precision() const {return m_precision;} | |||||
| int precision() const { | |||||
| return m_precision; | |||||
| } | |||||
| /** set precision | /** set precision | ||||
| * \param[in] n new precision | * \param[in] n new precision | ||||
| * \return old precision | * \return old precision | ||||
| m_fmtflags &= ~fl; | m_fmtflags &= ~fl; | ||||
| } | } | ||||
| /** \return width */ | /** \return width */ | ||||
| unsigned width() {return m_width;} | |||||
| unsigned width() { | |||||
| return m_width; | |||||
| } | |||||
| /** set width | /** set width | ||||
| * \param[in] n new width | * \param[in] n new width | ||||
| * \return old width | * \return old width | ||||
| return !fail() ? reinterpret_cast<const void*>(this) : 0; | return !fail() ? reinterpret_cast<const void*>(this) : 0; | ||||
| } | } | ||||
| /** \return true if fail() else false. */ | /** \return true if fail() else false. */ | ||||
| bool operator!() const {return fail();} | |||||
| bool operator!() const { | |||||
| return fail(); | |||||
| } | |||||
| /** \return The iostate flags for this file. */ | /** \return The iostate flags for this file. */ | ||||
| iostate rdstate() const {return m_iostate;} | |||||
| iostate rdstate() const { | |||||
| return m_iostate; | |||||
| } | |||||
| /** \return True if no iostate flags are set else false. */ | /** \return True if no iostate flags are set else false. */ | ||||
| bool good() const {return m_iostate == goodbit;} | |||||
| bool good() const { | |||||
| return m_iostate == goodbit; | |||||
| } | |||||
| /** \return true if end of file has been reached else false. | /** \return true if end of file has been reached else false. | ||||
| * | * | ||||
| * Warning: An empty file returns false before the first read. | * Warning: An empty file returns false before the first read. | ||||
| * Moral: eof() is only useful in combination with fail(), to find out | * Moral: eof() is only useful in combination with fail(), to find out | ||||
| * whether EOF was the cause for failure | * whether EOF was the cause for failure | ||||
| */ | */ | ||||
| bool eof() const {return m_iostate & eofbit;} | |||||
| bool eof() const { | |||||
| return m_iostate & eofbit; | |||||
| } | |||||
| /** \return true if any iostate bit other than eof are set else false. */ | /** \return true if any iostate bit other than eof are set else false. */ | ||||
| bool fail() const {return m_iostate & (failbit | badbit);} | |||||
| bool fail() const { | |||||
| return m_iostate & (failbit | badbit); | |||||
| } | |||||
| /** \return true if bad bit is set else false. */ | /** \return true if bad bit is set else false. */ | ||||
| bool bad() const {return m_iostate & badbit;} | |||||
| bool bad() const { | |||||
| return m_iostate & badbit; | |||||
| } | |||||
| /** Clear iostate bits. | /** Clear iostate bits. | ||||
| * | * | ||||
| * \param[in] state The flags you want to set after clearing all flags. | * \param[in] state The flags you want to set after clearing all flags. | ||||
| **/ | **/ | ||||
| void clear(iostate state = goodbit) {m_iostate = state;} | |||||
| void clear(iostate state = goodbit) { | |||||
| m_iostate = state; | |||||
| } | |||||
| /** Set iostate bits. | /** Set iostate bits. | ||||
| * | * | ||||
| * \param[in] state Bitts to set. | * \param[in] state Bitts to set. | ||||
| **/ | **/ | ||||
| void setstate(iostate state) {m_iostate |= state;} | |||||
| void setstate(iostate state) { | |||||
| m_iostate |= state; | |||||
| } | |||||
| private: | private: | ||||
| iostate m_iostate; | iostate m_iostate; |
| * along with the FatLib Library. If not, see | * along with the FatLib Library. If not, see | ||||
| * <http://www.gnu.org/licenses/>. | * <http://www.gnu.org/licenses/>. | ||||
| */ | */ | ||||
| #include <ctype.h> | |||||
| // #include <ctype.h> | |||||
| #include <float.h> | #include <float.h> | ||||
| #include "istream.h" | #include "istream.h" | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| istream& istream::get(char& c) { | istream& istream::get(char& c) { | ||||
| int tmp = get(); | int tmp = get(); | ||||
| if (tmp >= 0) c = tmp; | |||||
| if (tmp >= 0) { | |||||
| c = tmp; | |||||
| } | |||||
| return *this; | return *this; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| } | } | ||||
| str[m_gcount++] = c; | str[m_gcount++] = c; | ||||
| } | } | ||||
| if (n > 0) str[m_gcount] = '\0'; | |||||
| if (m_gcount == 0) setstate(failbit); | |||||
| if (n > 0) { | |||||
| str[m_gcount] = '\0'; | |||||
| } | |||||
| if (m_gcount == 0) { | |||||
| setstate(failbit); | |||||
| } | |||||
| return *this; | return *this; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| while (1) { | while (1) { | ||||
| falseOk = falseOk && c == pgm_read_byte(falsePtr + i); | falseOk = falseOk && c == pgm_read_byte(falsePtr + i); | ||||
| trueOk = trueOk && c == pgm_read_byte(truePtr + i); | trueOk = trueOk && c == pgm_read_byte(truePtr + i); | ||||
| if (trueOk == false && falseOk == false) break; | |||||
| if (trueOk == false && falseOk == false) { | |||||
| break; | |||||
| } | |||||
| i++; | i++; | ||||
| if (trueOk && i == true_len) { | if (trueOk && i == true_len) { | ||||
| *b = true; | *b = true; | ||||
| got_digit = true; | got_digit = true; | ||||
| if (frac < uint32_max/10) { | if (frac < uint32_max/10) { | ||||
| frac = frac * 10 + (c - '0'); | frac = frac * 10 + (c - '0'); | ||||
| if (got_dot) fracExp--; | |||||
| if (got_dot) { | |||||
| fracExp--; | |||||
| } | |||||
| } else { | } else { | ||||
| if (!got_dot) fracExp++; | |||||
| if (!got_dot) { | |||||
| fracExp++; | |||||
| } | |||||
| } | } | ||||
| } else if (!got_dot && c == '.') { | } else if (!got_dot && c == '.') { | ||||
| got_dot = true; | got_dot = true; | ||||
| } else { | } else { | ||||
| break; | break; | ||||
| } | } | ||||
| if (fracExp < -EXP_LIMIT || fracExp > EXP_LIMIT) goto fail; | |||||
| if (fracExp < -EXP_LIMIT || fracExp > EXP_LIMIT) { | |||||
| goto fail; | |||||
| } | |||||
| c = getch(&endPos); | c = getch(&endPos); | ||||
| } | } | ||||
| if (!got_digit) goto fail; | |||||
| if (!got_digit) { | |||||
| goto fail; | |||||
| } | |||||
| if (c == 'e' || c == 'E') { | if (c == 'e' || c == 'E') { | ||||
| c = getch(); | c = getch(); | ||||
| expNeg = c == '-'; | expNeg = c == '-'; | ||||
| c = getch(); | c = getch(); | ||||
| } | } | ||||
| while (isdigit(c)) { | while (isdigit(c)) { | ||||
| if (exp > EXP_LIMIT) goto fail; | |||||
| if (exp > EXP_LIMIT) { | |||||
| goto fail; | |||||
| } | |||||
| exp = exp * 10 + (c - '0'); | exp = exp * 10 + (c - '0'); | ||||
| c = getch(&endPos); | c = getch(&endPos); | ||||
| } | } | ||||
| v = static_cast<double>(frac); | v = static_cast<double>(frac); | ||||
| exp = expNeg ? fracExp - exp : fracExp + exp; | exp = expNeg ? fracExp - exp : fracExp + exp; | ||||
| expNeg = exp < 0; | expNeg = exp < 0; | ||||
| if (expNeg) exp = -exp; | |||||
| if (expNeg) { | |||||
| exp = -exp; | |||||
| } | |||||
| pow10 = 10.0; | pow10 = 10.0; | ||||
| while (exp) { | while (exp) { | ||||
| if (exp & 1) { | if (exp & 1) { | ||||
| if (expNeg) { | if (expNeg) { | ||||
| // check for underflow | // check for underflow | ||||
| if (v < FLT_MIN * pow10 && frac != 0) goto fail; | |||||
| if (v < FLT_MIN * pow10 && frac != 0) { | |||||
| goto fail; | |||||
| } | |||||
| v /= pow10; | v /= pow10; | ||||
| } else { | } else { | ||||
| // check for overflow | // check for overflow | ||||
| if (v > FLT_MAX / pow10) goto fail; | |||||
| if (v > FLT_MAX / pow10) { | |||||
| goto fail; | |||||
| } | |||||
| v *= pow10; | v *= pow10; | ||||
| } | } | ||||
| } | } | ||||
| *value = neg ? -v : v; | *value = neg ? -v : v; | ||||
| return true; | return true; | ||||
| fail: | |||||
| fail: | |||||
| // error restore position to last good place | // error restore position to last good place | ||||
| setpos(&endPos); | setpos(&endPos); | ||||
| setstate(failbit); | setstate(failbit); | ||||
| FatPos_t pos; | FatPos_t pos; | ||||
| int c; | int c; | ||||
| m_gcount = 0; | m_gcount = 0; | ||||
| if (n > 0) str[0] = '\0'; | |||||
| if (n > 0) { | |||||
| str[0] = '\0'; | |||||
| } | |||||
| while (1) { | while (1) { | ||||
| c = getch(&pos); | c = getch(&pos); | ||||
| if (c < 0) { | if (c < 0) { | ||||
| str[m_gcount++] = c; | str[m_gcount++] = c; | ||||
| str[m_gcount] = '\0'; | str[m_gcount] = '\0'; | ||||
| } | } | ||||
| if (m_gcount == 0) setstate(failbit); | |||||
| if (m_gcount == 0) { | |||||
| setstate(failbit); | |||||
| } | |||||
| return *this; | return *this; | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| } | } | ||||
| } | } | ||||
| str[i] = '\0'; | str[i] = '\0'; | ||||
| if (i == 0) setstate(failbit); | |||||
| if (i == 0) { | |||||
| setstate(failbit); | |||||
| } | |||||
| width(0); | width(0); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| break; | break; | ||||
| } | } | ||||
| m_gcount++; | m_gcount++; | ||||
| if (c == delim) break; | |||||
| if (c == delim) { | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| getpos(&pos); | getpos(&pos); | ||||
| c = getch(); | c = getch(); | ||||
| if (c < 0) { | if (c < 0) { | ||||
| if (!bad()) setstate(eofbit); | |||||
| if (!bad()) { | |||||
| setstate(eofbit); | |||||
| } | |||||
| } else { | } else { | ||||
| setpos(&pos); | setpos(&pos); | ||||
| } | } |
| pf(*this); | pf(*this); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a character string | |||||
| * \param[out] str location to store the string. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a character string | |||||
| * \param[out] str location to store the string. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream& operator>>(char *str) { | istream& operator>>(char *str) { | ||||
| getStr(str); | getStr(str); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a character | |||||
| * \param[out] ch location to store the character. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a character | |||||
| * \param[out] ch location to store the character. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream& operator>>(char& ch) { | istream& operator>>(char& ch) { | ||||
| getChar(&ch); | getChar(&ch); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a character string | |||||
| * \param[out] str location to store the string. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a character string | |||||
| * \param[out] str location to store the string. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream& operator>>(signed char *str) { | istream& operator>>(signed char *str) { | ||||
| getStr(reinterpret_cast<char*>(str)); | getStr(reinterpret_cast<char*>(str)); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a character | |||||
| * \param[out] ch location to store the character. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a character | |||||
| * \param[out] ch location to store the character. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream& operator>>(signed char& ch) { | istream& operator>>(signed char& ch) { | ||||
| getChar(reinterpret_cast<char*>(&ch)); | getChar(reinterpret_cast<char*>(&ch)); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a character string | |||||
| * \param[out] str location to store the string. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a character string | |||||
| * \param[out] str location to store the string. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream& operator>>(unsigned char *str) { | istream& operator>>(unsigned char *str) { | ||||
| getStr(reinterpret_cast<char*>(str)); | getStr(reinterpret_cast<char*>(str)); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a character | |||||
| * \param[out] ch location to store the character. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a character | |||||
| * \param[out] ch location to store the character. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream& operator>>(unsigned char& ch) { | istream& operator>>(unsigned char& ch) { | ||||
| getChar(reinterpret_cast<char*>(&ch)); | getChar(reinterpret_cast<char*>(&ch)); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a value of type bool. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a value of type bool. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream& operator>>(bool& arg) { | istream& operator>>(bool& arg) { | ||||
| getBool(&arg); | getBool(&arg); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a value of type short. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a value of type short. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream &operator>>(short& arg) { // NOLINT | istream &operator>>(short& arg) { // NOLINT | ||||
| getNumber(&arg); | getNumber(&arg); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a value of type unsigned short. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a value of type unsigned short. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream &operator>>(unsigned short& arg) { // NOLINT | istream &operator>>(unsigned short& arg) { // NOLINT | ||||
| getNumber(&arg); | getNumber(&arg); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a value of type int. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a value of type int. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream &operator>>(int& arg) { | istream &operator>>(int& arg) { | ||||
| getNumber(&arg); | getNumber(&arg); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a value of type unsigned int. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a value of type unsigned int. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream &operator>>(unsigned int& arg) { | istream &operator>>(unsigned int& arg) { | ||||
| getNumber(&arg); | getNumber(&arg); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a value of type long. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a value of type long. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream &operator>>(long& arg) { // NOLINT | istream &operator>>(long& arg) { // NOLINT | ||||
| getNumber(&arg); | getNumber(&arg); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a value of type unsigned long. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a value of type unsigned long. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream &operator>>(unsigned long& arg) { // NOLINT | istream &operator>>(unsigned long& arg) { // NOLINT | ||||
| getNumber(&arg); | getNumber(&arg); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| /** | |||||
| * Extract a value of type double. | * Extract a value of type double. | ||||
| * \param[out] arg location to store the value. | * \param[out] arg location to store the value. | ||||
| * \return Is always *this. Failure is indicated by the state of *this. | * \return Is always *this. Failure is indicated by the state of *this. | ||||
| getDouble(&arg); | getDouble(&arg); | ||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | |||||
| * Extract a value of type float. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| /** | |||||
| * Extract a value of type float. | |||||
| * \param[out] arg location to store the value. | |||||
| * \return Is always *this. Failure is indicated by the state of *this. | |||||
| */ | |||||
| istream &operator>> (float& arg) { | istream &operator>> (float& arg) { | ||||
| double v; | double v; | ||||
| getDouble(&v); | getDouble(&v); | ||||
| * \return The number of characters extracted by the last unformatted | * \return The number of characters extracted by the last unformatted | ||||
| * input function. | * input function. | ||||
| */ | */ | ||||
| streamsize gcount() const {return m_gcount;} | |||||
| streamsize gcount() const { | |||||
| return m_gcount; | |||||
| } | |||||
| /** | /** | ||||
| * Extract a character if one is available. | * Extract a character if one is available. | ||||
| * | * | ||||
| * \return The character or -1 if a failure occurs. A failure is indicated | * \return The character or -1 if a failure occurs. A failure is indicated | ||||
| * by the stream state. | * by the stream state. | ||||
| */ | |||||
| */ | |||||
| int get(); | int get(); | ||||
| /** | /** | ||||
| * Extract a character if one is available. | * Extract a character if one is available. | ||||
| * \param[out] ch location to receive the extracted character. | * \param[out] ch location to receive the extracted character. | ||||
| * | * | ||||
| * \return always returns *this. A failure is indicated by the stream state. | * \return always returns *this. A failure is indicated by the stream state. | ||||
| */ | |||||
| */ | |||||
| istream& get(char& ch); | istream& get(char& ch); | ||||
| /** | /** | ||||
| * Extract characters. | |||||
| * Extract characters. | |||||
| * | * | ||||
| * \param[out] str Location to receive extracted characters. | * \param[out] str Location to receive extracted characters. | ||||
| * \param[in] n Size of str. | * \param[in] n Size of str. | ||||
| * failbit is set. If end-of-file occurs the eofbit is set. | * failbit is set. If end-of-file occurs the eofbit is set. | ||||
| * | * | ||||
| * \return always returns *this. A failure is indicated by the stream state. | * \return always returns *this. A failure is indicated by the stream state. | ||||
| */ | |||||
| */ | |||||
| istream& get(char *str, streamsize n, char delim = '\n'); | istream& get(char *str, streamsize n, char delim = '\n'); | ||||
| /** | /** | ||||
| * Extract characters | * Extract characters | ||||
| * | * | ||||
| * \return *this | * \return *this | ||||
| * | * | ||||
| */ | |||||
| */ | |||||
| istream& ignore(streamsize n = 1, int delim= -1); | istream& ignore(streamsize n = 1, int delim= -1); | ||||
| /** | /** | ||||
| * Return the next available character without consuming it. | * Return the next available character without consuming it. | ||||
| * | * | ||||
| * \return The character if the stream state is good else -1; | * \return The character if the stream state is good else -1; | ||||
| * | * | ||||
| */ | |||||
| */ | |||||
| int peek(); | int peek(); | ||||
| // istream& read(char *str, streamsize count); | // istream& read(char *str, streamsize count); | ||||
| // streamsize readsome(char *str, streamsize count); | // streamsize readsome(char *str, streamsize count); | ||||
| /** | /** | ||||
| * \return the stream position | * \return the stream position | ||||
| */ | */ | ||||
| pos_type tellg() {return tellpos();} | |||||
| pos_type tellg() { | |||||
| return tellpos(); | |||||
| } | |||||
| /** | /** | ||||
| * Set the stream position | * Set the stream position | ||||
| * \param[in] pos The absolute position in which to move the read pointer. | * \param[in] pos The absolute position in which to move the read pointer. | ||||
| * \return Is always *this. Failure is indicated by the state of *this. | * \return Is always *this. Failure is indicated by the state of *this. | ||||
| */ | */ | ||||
| istream& seekg(pos_type pos) { | istream& seekg(pos_type pos) { | ||||
| if (!seekpos(pos)) setstate(failbit); | |||||
| if (!seekpos(pos)) { | |||||
| setstate(failbit); | |||||
| } | |||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | /** | ||||
| * \return Is always *this. Failure is indicated by the state of *this. | * \return Is always *this. Failure is indicated by the state of *this. | ||||
| */ | */ | ||||
| istream& seekg(off_type off, seekdir way) { | istream& seekg(off_type off, seekdir way) { | ||||
| if (!seekoff(off, way)) setstate(failbit); | |||||
| if (!seekoff(off, way)) { | |||||
| setstate(failbit); | |||||
| } | |||||
| return *this; | return *this; | ||||
| } | } | ||||
| void skipWhite(); | void skipWhite(); | ||||
| protected: | protected: | ||||
| /// @cond SHOW_PROTECTED | /// @cond SHOW_PROTECTED | ||||
| /** | |||||
| * Internal - do not use | |||||
| * \return | |||||
| */ | |||||
| /** | |||||
| * Internal - do not use | |||||
| * \return | |||||
| */ | |||||
| virtual int16_t getch() = 0; | virtual int16_t getch() = 0; | ||||
| /** | /** | ||||
| * Internal - do not use | * Internal - do not use |
| #endif | #endif | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void ostream::do_fill(unsigned len) { | void ostream::do_fill(unsigned len) { | ||||
| for (; len < width(); len++) putch(fill()); | |||||
| for (; len < width(); len++) { | |||||
| putch(fill()); | |||||
| } | |||||
| width(0); | width(0); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| return; | return; | ||||
| } | } | ||||
| // round up and separate int and fraction parts | // round up and separate int and fraction parts | ||||
| for (uint8_t i = 0; i < nd; ++i) round *= 0.1; | |||||
| for (uint8_t i = 0; i < nd; ++i) { | |||||
| round *= 0.1; | |||||
| } | |||||
| n += round; | n += round; | ||||
| uint32_t intPart = n; | uint32_t intPart = n; | ||||
| double fractionPart = n - intPart; | double fractionPart = n - intPart; | ||||
| // format intPart and decimal point | // format intPart and decimal point | ||||
| if (nd || (flags() & showpoint)) *--str = '.'; | |||||
| if (nd || (flags() & showpoint)) { | |||||
| *--str = '.'; | |||||
| } | |||||
| str = fmtNum(intPart, str, 10); | str = fmtNum(intPart, str, 10); | ||||
| // calculate length for fill | // calculate length for fill | ||||
| // extract adjust field | // extract adjust field | ||||
| fmtflags adj = flags() & adjustfield; | fmtflags adj = flags() & adjustfield; | ||||
| if (adj == internal) { | if (adj == internal) { | ||||
| if (sign) putch(sign); | |||||
| if (sign) { | |||||
| putch(sign); | |||||
| } | |||||
| do_fill(len); | do_fill(len); | ||||
| } else { | } else { | ||||
| // do fill for internal or right | // do fill for internal or right | ||||
| fill_not_left(len); | fill_not_left(len); | ||||
| if (sign) *--str = sign; | |||||
| if (sign) { | |||||
| *--str = sign; | |||||
| } | |||||
| } | } | ||||
| putstr(str); | putstr(str); | ||||
| // output fraction | // output fraction | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void ostream::putNum(int32_t n) { | void ostream::putNum(int32_t n) { | ||||
| bool neg = n < 0 && flagsToBase() == 10; | bool neg = n < 0 && flagsToBase() == 10; | ||||
| if (neg) n = -n; | |||||
| if (neg) { | |||||
| n = -n; | |||||
| } | |||||
| putNum(n, neg); | putNum(n, neg); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| uint8_t len = end - str; | uint8_t len = end - str; | ||||
| fmtflags adj = flags() & adjustfield; | fmtflags adj = flags() & adjustfield; | ||||
| if (adj == internal) { | if (adj == internal) { | ||||
| while (str < num) putch(*str++); | |||||
| while (str < num) { | |||||
| putch(*str++); | |||||
| } | |||||
| } | } | ||||
| if (adj != left) { | if (adj != left) { | ||||
| do_fill(len); | do_fill(len); |
| * \return A reference to the ostream object. | * \return A reference to the ostream object. | ||||
| */ | */ | ||||
| ostream& flush() { | ostream& flush() { | ||||
| if (!sync()) setstate(badbit); | |||||
| if (!sync()) { | |||||
| setstate(badbit); | |||||
| } | |||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | /** | ||||
| * \return the stream position | * \return the stream position | ||||
| */ | */ | ||||
| pos_type tellp() {return tellpos();} | |||||
| pos_type tellp() { | |||||
| return tellpos(); | |||||
| } | |||||
| /** | /** | ||||
| * Set the stream position | * Set the stream position | ||||
| * \param[in] pos The absolute position in which to move the write pointer. | * \param[in] pos The absolute position in which to move the write pointer. | ||||
| * \return Is always *this. Failure is indicated by the state of *this. | * \return Is always *this. Failure is indicated by the state of *this. | ||||
| */ | */ | ||||
| ostream& seekp(pos_type pos) { | ostream& seekp(pos_type pos) { | ||||
| if (!seekpos(pos)) setstate(failbit); | |||||
| if (!seekpos(pos)) { | |||||
| setstate(failbit); | |||||
| } | |||||
| return *this; | return *this; | ||||
| } | } | ||||
| /** | /** | ||||
| * \return Is always *this. Failure is indicated by the state of *this. | * \return Is always *this. Failure is indicated by the state of *this. | ||||
| */ | */ | ||||
| ostream& seekp(off_type off, seekdir way) { | ostream& seekp(off_type off, seekdir way) { | ||||
| if (!seekoff(off, way)) setstate(failbit); | |||||
| if (!seekoff(off, way)) { | |||||
| setstate(failbit); | |||||
| } | |||||
| return *this; | return *this; | ||||
| } | } | ||||
| while (Serial.read() <= 0) {} | while (Serial.read() <= 0) {} | ||||
| delay(200); // Catch Due reset problem | delay(200); // Catch Due reset problem | ||||
| print_P(testOut, PSTR("FreeRam: ")); | |||||
| testOut->print(F("FreeRam: ")); | |||||
| testOut->println(FreeRam()); | testOut->println(FreeRam()); | ||||
| testOut->println(); | testOut->println(); | ||||
| failCount = 0; | failCount = 0; | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void testEnd() { | void testEnd() { | ||||
| testOut->println(); | testOut->println(); | ||||
| println_P(testOut, PSTR("Compiled: " __DATE__ " " __TIME__)); | |||||
| print_P(testOut, PSTR("FreeRam: ")); | |||||
| testOut->println(F("Compiled: " __DATE__ " " __TIME__)); | |||||
| testOut->print(F("FreeRam: ")); | |||||
| testOut->println(FreeRam()); | testOut->println(FreeRam()); | ||||
| print_P(testOut, PSTR("Test count: ")); | |||||
| testOut->print(F("Test count: ")); | |||||
| testOut->println(testCount); | testOut->println(testCount); | ||||
| print_P(testOut, PSTR("Fail count: ")); | |||||
| testOut->print(F("Fail count: ")); | |||||
| testOut->println(failCount); | testOut->println(failCount); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| static void testResult(bool b, uint8_t n) { | static void testResult(bool b, uint8_t n) { | ||||
| while (n++ < 60) testOut->write(' '); | while (n++ < 60) testOut->write(' '); | ||||
| if (b) { | if (b) { | ||||
| println_P(testOut, PSTR("..ok")); | |||||
| testOut->println(F("..ok")); | |||||
| } else { | } else { | ||||
| println_P(testOut, PSTR("FAIL")); | |||||
| testOut->println(F("FAIL")); | |||||
| failCount++; | failCount++; | ||||
| } | } | ||||
| testCount++; | testCount++; | ||||
| testOut->write('"'); | testOut->write('"'); | ||||
| testOut->print(result); | testOut->print(result); | ||||
| testOut->print("\",\""); | testOut->print("\",\""); | ||||
| print_P(testOut, expect); | |||||
| testOut->print((const __FlashStringHelper*)expect); | |||||
| testOut->write('"'); | testOut->write('"'); | ||||
| uint8_t n = strlen(result) + strlenPGM(expect) + 5; | uint8_t n = strlen(result) + strlenPGM(expect) + 5; | ||||
| testResult(!strcmp_P(result, expect), n); | testResult(!strcmp_P(result, expect), n); | ||||
| } | } | ||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||
| void testVerify_P(bool b, PGM_P msg) { | void testVerify_P(bool b, PGM_P msg) { | ||||
| print_P(testOut, msg); | |||||
| testOut->print((const __FlashStringHelper*)msg); | |||||
| uint8_t n = strlenPGM(msg); | uint8_t n = strlenPGM(msg); | ||||
| testResult(b, n); | testResult(b, n); | ||||
| } | } |
| * create enough files to force a cluster to be allocated to dir. | * create enough files to force a cluster to be allocated to dir. | ||||
| */ | */ | ||||
| void dirAllocTest(SdBaseFile* dir) { | void dirAllocTest(SdBaseFile* dir) { | ||||
| char buf[13], name[13]; | |||||
| char buf[32], name[32]; | |||||
| SdFile file; | SdFile file; | ||||
| uint16_t n; | uint16_t n; | ||||
| uint32_t size = dir->fileSize(); | |||||
| uint32_t size = dir->dirSize(); | |||||
| // create files and write name to file | // create files and write name to file | ||||
| for (n = 0; ; n++){ | for (n = 0; ; n++){ | ||||
| Serial.println(t2 - t1); | Serial.println(t2 - t1); | ||||
| // directory size will change when a cluster is added | // directory size will change when a cluster is added | ||||
| if (dir->fileSize() != size) break; | |||||
| if (dir->curPosition() > size) break; | |||||
| } | } | ||||
| // read files and check content | // read files and check content | ||||
| // open end time and read start time | // open end time and read start time | ||||
| uint32_t t1 = millis(); | uint32_t t1 = millis(); | ||||
| int16_t nr = file.read(buf, 13); | |||||
| int16_t nr = file.read(buf, sizeof(buf)); | |||||
| if (nr < 5) error("file.read failed"); | if (nr < 5) error("file.read failed"); | ||||
| // read end time | // read end time | ||||
| // try SPI_HALF_SPEED if bus errors occur. | // try SPI_HALF_SPEED if bus errors occur. | ||||
| if (!sd.begin(SD_CHIP_SELECT, SPI_FULL_SPEED)) sd.initErrorHalt(); | if (!sd.begin(SD_CHIP_SELECT, SPI_FULL_SPEED)) sd.initErrorHalt(); | ||||
| uint32_t m = millis(); | |||||
| // write files to root if FAT32 | // write files to root if FAT32 | ||||
| if (sd.vol()->fatType() == 32) { | if (sd.vol()->fatType() == 32) { | ||||
| Serial.println(F("Writing files to root")); | Serial.println(F("Writing files to root")); | ||||
| if (!sub2.mkdir(&sub1, "SUB2")) error("mkdir SUB2 failed"); | if (!sub2.mkdir(&sub1, "SUB2")) error("mkdir SUB2 failed"); | ||||
| Serial.println(F("Writing files to SUB2")); | Serial.println(F("Writing files to SUB2")); | ||||
| dirAllocTest(&sub2); | dirAllocTest(&sub2); | ||||
| Serial.println(F("Done")); | |||||
| m = millis() - m; | |||||
| Serial.print(F("Done millis: ")); | |||||
| Serial.println(m); | |||||
| } | } | ||||
| void loop() { } | |||||
| void loop() { } |
| * remove all files in dir. | * remove all files in dir. | ||||
| */ | */ | ||||
| void deleteFiles(SdBaseFile* dir) { | void deleteFiles(SdBaseFile* dir) { | ||||
| char name[13]; | |||||
| char name[32]; | |||||
| SdFile file; | SdFile file; | ||||
| // open and delete files | // open and delete files |
| // Program to compare size of SdFat with the SD.h library. | |||||
| // Select the test library by commenting out one of the following two lines. | |||||
| // #include <SD.h> | |||||
| #include <SdFat.h> | |||||
| // SD chip select pin. | |||||
| const uint8_t SD_CS_PIN = SS; | |||||
| #ifdef __SD_H__ | |||||
| File file; | |||||
| #else // __SD_H__ | |||||
| SdFat SD; | |||||
| SdFile file; | |||||
| #endif // __SD_H__ | |||||
| void setup() { | |||||
| Serial.begin(9600); | |||||
| while (!Serial) {} // wait for Leonardo | |||||
| if (!SD.begin(SD_CS_PIN)) { | |||||
| Serial.println("begin failed"); | |||||
| return; | |||||
| } | |||||
| #ifdef __SD_H__ | |||||
| file = SD.open("SFN_file.txt", FILE_WRITE); | |||||
| #else // __SD_H__ | |||||
| file.open("LFN_file.txt", O_RDWR | O_CREAT); | |||||
| #endif // __SD_H__ | |||||
| file.println("Hello"); | |||||
| file.close(); | |||||
| Serial.println("Done"); | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| void loop() {} |
| #include <SPI.h> | |||||
| #include <SdFat.h> | |||||
| #include <SdFatUtil.h> | |||||
| const uint8_t SD_CS_PIN = SS; | |||||
| SdFat sd; | |||||
| SdFile file; | |||||
| char name[260]; | |||||
| //------------------------------------------------------------------------------ | |||||
| char* testName[] = { | |||||
| "low.low", | |||||
| "low.Mix", | |||||
| "low.UP", | |||||
| "Mix.low", | |||||
| "Mix.Mix", | |||||
| "Mix.UP", | |||||
| "UP.low", | |||||
| "UP.Mix", | |||||
| "UP.UP", | |||||
| ".dot", | |||||
| ".dot.dot", | |||||
| "A b c . txt", | |||||
| " Leading space and no extension", | |||||
| "Trailing dots and space . . .", | |||||
| "Long extension.extension", | |||||
| "Space after dot. txt", | |||||
| "Dot.dot.test.txt", | |||||
| "Dot.dot.test.seq.txt", | |||||
| "LOW.LOW", | |||||
| "MIX.MIX", | |||||
| "Invalid character *.test" | |||||
| }; | |||||
| //------------------------------------------------------------------------------ | |||||
| bool checkName(char first, size_t len) { | |||||
| size_t i; | |||||
| if (len < 5 || len > sizeof(name)) { | |||||
| return false; | |||||
| } | |||||
| if ( name[0] != first) { | |||||
| return false; | |||||
| } | |||||
| for (i = 1; i < (len - 4); i++) { | |||||
| if (name[i] != ('0' + (i + 1) %10)) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| char* p = ".txt"; | |||||
| while (*p) { | |||||
| if (name[i++] != *p++) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| return name[i] == 0; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| void makeName(char first, size_t len) { | |||||
| size_t i; | |||||
| if (len > sizeof(name)) { | |||||
| len = 255; | |||||
| } | |||||
| if (len < 5) { | |||||
| len = 5; | |||||
| } | |||||
| name[0] = first; | |||||
| for (i = 1; i < (len - 4); i++) { | |||||
| name[i] = '0' + (i + 1) %10; | |||||
| } | |||||
| char* p = ".txt"; | |||||
| while (*p) name[i++] = *p++; | |||||
| name[i] = 0; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| // test open, remove, getFilename, and ls. | |||||
| void basicTest() { | |||||
| size_t i; | |||||
| size_t n = sd.vol()->fatType() == 32 ? 255 : 99; | |||||
| uint16_t index; | |||||
| uint16_t maxIndex = 0; | |||||
| makeName('Z', 256); | |||||
| if (!file.open(name, O_RDWR | O_CREAT)) { | |||||
| Serial.println(F("255 limit OK")); | |||||
| } else { | |||||
| sd.errorHalt(F("255 limit")); | |||||
| } | |||||
| for (i = 5; i <= n; i++) { | |||||
| makeName('A', i); | |||||
| if (!file.open(name, O_RDWR | O_CREAT)) { | |||||
| sd.errorHalt(F("open A")); | |||||
| } | |||||
| file.println(name); | |||||
| Serial.print(i); | |||||
| Serial.write(' '); | |||||
| Serial.print(file.dirIndex()); | |||||
| Serial.write(' '); | |||||
| Serial.print(file.fileSize()); | |||||
| Serial.println(F(" open A")); | |||||
| if (file.fileSize() != (i + 2)) { | |||||
| sd.errorHalt(F("file size A")); | |||||
| } | |||||
| if (file.dirIndex() >= maxIndex) { | |||||
| maxIndex = file.dirIndex(); | |||||
| } else { | |||||
| Serial.print(maxIndex); Serial.print(',');Serial.println(file.dirIndex()); | |||||
| sd.errorHalt(F("dirIndex")); | |||||
| } | |||||
| file.close(); | |||||
| if (!file.open(sd.vwd(), maxIndex, O_READ)) { | |||||
| sd.errorHalt(F("open by index")); | |||||
| } | |||||
| memset(name, 0, sizeof(name)); | |||||
| if (!file.getFilename(name, sizeof(name))) { | |||||
| sd.errorHalt(F("getFilename")); | |||||
| } | |||||
| if (!checkName('A', i)) { | |||||
| Serial.println(name); | |||||
| sd.errorHalt(F("checkName")); | |||||
| } | |||||
| file.close(); | |||||
| } | |||||
| for (i = n; i >= 5; i -= 2) { | |||||
| makeName('A', i); | |||||
| Serial.print(i); | |||||
| Serial.println(F( " rm A")); | |||||
| if (!sd.remove(name)) { | |||||
| sd.errorHalt(F("remove A")); | |||||
| } | |||||
| } | |||||
| for (i = n; i >= 5; i -= 2) { | |||||
| makeName('B', i); | |||||
| if (!file.open(name, O_RDWR | O_CREAT)) { | |||||
| sd.errorHalt(F("open B")); | |||||
| } | |||||
| file.println(name); | |||||
| Serial.print(i); | |||||
| Serial.write(' '); | |||||
| Serial.print(file.dirIndex()); | |||||
| Serial.write(' '); | |||||
| Serial.print(file.fileSize()); | |||||
| Serial.println(F(" open B")); | |||||
| if (file.fileSize() != (i + 2)) { | |||||
| sd.errorHalt(F("file size B")); | |||||
| } | |||||
| if (file.dirIndex() > maxIndex) { | |||||
| sd.errorHalt(F("maxIndex")); | |||||
| } | |||||
| file.close(); | |||||
| } | |||||
| Serial.println(F("----- ls ------")); | |||||
| sd.ls(); | |||||
| for (i = 5; i <= n; i++) { | |||||
| char fc = i & 1 ? 'B' : 'A'; | |||||
| makeName(fc, i); | |||||
| Serial.print(i); | |||||
| Serial.print(F(" rm ")); | |||||
| Serial.println(fc); | |||||
| if (!sd.remove(name)) { | |||||
| sd.errorHalt(F("remove A/B")); | |||||
| } | |||||
| } | |||||
| if (file.openNext(sd.vwd())) { | |||||
| sd.errorHalt(F("remove all")); | |||||
| } | |||||
| Serial.println(); | |||||
| Serial.println(F("basicTest done")); | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| void nameTest() { | |||||
| Serial.println(); | |||||
| uint8_t n = sizeof(testName)/sizeof(char*); | |||||
| for (uint8_t i = 0; i < n; i++) { | |||||
| Serial.print(F("Name: ")); | |||||
| Serial.write('"'); | |||||
| Serial.print(testName[i]); | |||||
| Serial.println('"'); | |||||
| if(!file.open(testName[i], O_CREAT | O_RDWR)) { | |||||
| Serial.println(F("Open failed")); | |||||
| } else { | |||||
| file.println(testName[i]); | |||||
| if (!file.getFilename(name, sizeof(name))) { | |||||
| sd.errorHalt(F("getFilemame")); | |||||
| } | |||||
| file.println(name); | |||||
| Serial.print(F("LFN: ")); | |||||
| Serial.write('"'); | |||||
| Serial.print(name); | |||||
| Serial.println('"'); | |||||
| Serial.print(F("SFN: ")); | |||||
| Serial.write('"'); | |||||
| file.printSFN(&Serial); | |||||
| Serial.println('"'); | |||||
| Serial.print(F("Index: ")); | |||||
| if (file.dirIndex() < 10) { | |||||
| Serial.write(' '); | |||||
| } | |||||
| Serial.println(file.dirIndex()); | |||||
| file.close(); | |||||
| } | |||||
| Serial.println(); | |||||
| } | |||||
| Serial.println(F("----- ls ------")); | |||||
| sd.ls(); | |||||
| Serial.println(); | |||||
| Serial.println(F("nameTest done")); | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| void setup() { | |||||
| Serial.begin(9600); | |||||
| while(!Serial); | |||||
| Serial.print(F("\r\nFreeRam: ")); | |||||
| Serial.println(FreeRam()); | |||||
| Serial.println(F("Type any character to start.")); | |||||
| while (Serial.read() < 0) {} | |||||
| if (!sd.begin(SD_CS_PIN)) sd.initErrorHalt(); | |||||
| if (file.openNext(sd.vwd())) { | |||||
| file.close(); | |||||
| delay(100); | |||||
| while (Serial.read() >= 0) {} | |||||
| Serial.print(F("Type 'W' to wipe the card: ")); | |||||
| int c; | |||||
| while ((c = Serial.read()) < 0) {} | |||||
| if (c != 'W') { | |||||
| sd.errorHalt(F("Invalid")); | |||||
| } | |||||
| Serial.println((char)c); | |||||
| if (!sd.wipe(&Serial) || !sd.begin(SD_CS_PIN)) { | |||||
| sd.errorHalt(F("wipe failed")); | |||||
| } | |||||
| } | |||||
| basicTest(); | |||||
| nameTest(); | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| void loop() {} |
| #include <SPI.h> | |||||
| #include <SdFat.h> | |||||
| #include <SdFatUtil.h> | |||||
| const uint8_t SD_CS_PIN = SS; | |||||
| SdFat sd; | |||||
| SdFile file; | |||||
| char name[260]; | |||||
| // Serial output stream | |||||
| ArduinoOutStream cout(Serial); | |||||
| // Serial in buffer. | |||||
| char cinBuf[10]; | |||||
| // Serial input stream | |||||
| ArduinoInStream cin(Serial, cinBuf, sizeof(cinBuf)); | |||||
| //------------------------------------------------------------------------------ | |||||
| char* testName[] = { | |||||
| "low.low", | |||||
| "low.Mix", | |||||
| "low.UP", | |||||
| "Mix.low", | |||||
| "Mix.Mix", | |||||
| "Mix.UP", | |||||
| "UP.low", | |||||
| "UP.Mix", | |||||
| "UP.UP", | |||||
| ".dot", | |||||
| ".dot.dot", | |||||
| "A b c . txt", | |||||
| " Leading space and no extension", | |||||
| "Trailing dots and space . . .", | |||||
| "Long extension.extension", | |||||
| "Space after dot. txt", | |||||
| "Dot.dot.test.txt", | |||||
| "Dot.dot.test.seq.txt", | |||||
| "LOW.LOW", | |||||
| "MIX.MIX", | |||||
| "Invalid character *.test" | |||||
| }; | |||||
| //------------------------------------------------------------------------------ | |||||
| bool checkName(char first, size_t len) { | |||||
| size_t i; | |||||
| if (len < 5 || len > sizeof(name)) { | |||||
| return false; | |||||
| } | |||||
| if ( name[0] != first) { | |||||
| return false; | |||||
| } | |||||
| for (i = 1; i < (len - 4); i++) { | |||||
| if (name[i] != ('0' + (i + 1) %10)) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| char* p = ".txt"; | |||||
| while (*p) { | |||||
| if (name[i++] != *p++) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| return name[i] == 0; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| void makeName(char first, size_t len) { | |||||
| size_t i; | |||||
| if (len > sizeof(name)) { | |||||
| len = 255; | |||||
| } | |||||
| if (len < 5) { | |||||
| len = 5; | |||||
| } | |||||
| name[0] = first; | |||||
| for (i = 1; i < (len - 4); i++) { | |||||
| name[i] = '0' + (i + 1) %10; | |||||
| } | |||||
| char* p = ".txt"; | |||||
| while (*p) name[i++] = *p++; | |||||
| name[i] = 0; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| // test open, remove, getFilename, and ls. | |||||
| void basicTest() { | |||||
| size_t i; | |||||
| size_t n = sd.vol()->fatType() == 32 ? 255 : 99; | |||||
| uint16_t index; | |||||
| uint16_t maxIndex = 0; | |||||
| makeName('Z', 256); | |||||
| if (!file.open(name, O_RDWR | O_CREAT)) { | |||||
| cout << F("255 limit OK") << endl; | |||||
| } else { | |||||
| sd.errorHalt(F("255 limit")); | |||||
| } | |||||
| for (i = 5; i <= n; i++) { | |||||
| makeName('A', i); | |||||
| if (!file.open(name, O_RDWR | O_CREAT)) { | |||||
| sd.errorHalt(F("open A")); | |||||
| } | |||||
| file.println(name); | |||||
| cout << setw(3) << i << setw(5) << file.dirIndex() << F(" open A") << endl; | |||||
| if (file.fileSize() != (i + 2)) { | |||||
| sd.errorHalt(F("file size A")); | |||||
| } | |||||
| if (file.dirIndex() >= maxIndex) { | |||||
| maxIndex = file.dirIndex(); | |||||
| } else { | |||||
| sd.errorHalt(F("dirIndex")); | |||||
| } | |||||
| file.close(); | |||||
| if (!file.open(sd.vwd(), maxIndex, O_READ)) { | |||||
| sd.errorHalt(F("open by index")); | |||||
| } | |||||
| memset(name, 0, sizeof(name)); | |||||
| if (!file.getFilename(name, sizeof(name))) { | |||||
| sd.errorHalt(F("getFilename")); | |||||
| } | |||||
| if (!checkName('A', i)) { | |||||
| cout << name << endl; | |||||
| sd.errorHalt(F("checkName")); | |||||
| } | |||||
| file.close(); | |||||
| } | |||||
| for (i = n; i >= 5; i -= 2) { | |||||
| makeName('A', i); | |||||
| cout << setw(3) << i << F( " rm A") << endl; | |||||
| if (!sd.remove(name)) { | |||||
| sd.errorHalt(F("remove A")); | |||||
| } | |||||
| } | |||||
| for (i = n; i >= 5; i -= 2) { | |||||
| makeName('B', i); | |||||
| if (!file.open(name, O_RDWR | O_CREAT)) { | |||||
| sd.errorHalt(F("open B")); | |||||
| } | |||||
| file.println(name); | |||||
| cout << setw(3) << i << setw(5) << file.dirIndex() << F(" open B") << endl; | |||||
| if (file.fileSize() != (i + 2)) { | |||||
| sd.errorHalt(F("file size B")); | |||||
| } | |||||
| if (file.dirIndex() > maxIndex) { | |||||
| sd.errorHalt(F("maxIndex")); | |||||
| } | |||||
| file.close(); | |||||
| } | |||||
| cout << endl << F("----- ls ------") << endl; | |||||
| sd.ls(); | |||||
| for (i = 5; i <= n; i++) { | |||||
| char fc = i & 1 ? 'B' : 'A'; | |||||
| makeName(fc, i); | |||||
| cout << setw(3) << i << F(" rm ") << fc << endl; | |||||
| if (!sd.remove(name)) { | |||||
| sd.errorHalt(F("remove A/B")); | |||||
| } | |||||
| } | |||||
| if (file.openNext(sd.vwd())) { | |||||
| sd.errorHalt(F("remove all")); | |||||
| } | |||||
| cout << endl << F("basicTest done") << endl; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| void nameTest() { | |||||
| cout << endl; | |||||
| uint8_t n = sizeof(testName)/sizeof(char*); | |||||
| for (uint8_t i = 0; i < n; i++) { | |||||
| cout << F("Name: \"") << testName[i] << '"' << endl; | |||||
| if(!file.open(testName[i], O_CREAT | O_RDWR)) { | |||||
| cout <<F("Open failed") << endl; | |||||
| } else { | |||||
| file.println(testName[i]); | |||||
| if (!file.getFilename(name, sizeof(name))) { | |||||
| sd.errorHalt(F("getFilemame")); | |||||
| } | |||||
| cout << F("LFN: \"") << name << '"' << endl; | |||||
| cout << F("SFN: \""); | |||||
| file.printSFN(&Serial); | |||||
| cout << '"' << endl; | |||||
| cout <<F("Index: ") << setw(2) << file.dirIndex() << endl; | |||||
| file.close(); | |||||
| } | |||||
| cout << endl; | |||||
| } | |||||
| cout << F("----- ls ------") << endl; | |||||
| sd.ls(); | |||||
| cout << endl << F("nameTest done") << endl; | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| void setup() { | |||||
| Serial.begin(9600); | |||||
| while(!Serial); // Wait for USB Serial. | |||||
| cout << endl << F("FreeRam: ") << FreeRam() << endl; | |||||
| cout << F("Type any character to start.") << endl; | |||||
| cin.readline(); | |||||
| if (!sd.begin(SD_CS_PIN)) { | |||||
| sd.initErrorHalt(); | |||||
| } | |||||
| if (file.openNext(sd.vwd())) { | |||||
| file.close(); | |||||
| cout << F("Type 'W' to wipe the card: "); | |||||
| cin.readline(); | |||||
| char c = cin.get(); | |||||
| cout << c << endl; | |||||
| if (c != 'W') { | |||||
| sd.errorHalt(F("Invalid")); | |||||
| } | |||||
| if (!sd.wipe(&Serial) || !sd.begin(SD_CS_PIN)) { | |||||
| sd.errorHalt(F("wipe failed")); | |||||
| } | |||||
| } | |||||
| basicTest(); | |||||
| nameTest(); | |||||
| } | |||||
| //------------------------------------------------------------------------------ | |||||
| void loop() {} |
| Software SPI is now supported on AVR, Due, and Teensy 3.x boards. | |||||
| The most general approach is to set USE_MULTIPLE_SPI_TYPES nonzero | |||||
| in SdFatConfig.h. | |||||
| //------------------------------------------------------------------------------ | |||||
| /** | |||||
| * Set USE_MULTIPLE_SPI_TYPES nonzero to enable the SdFatSoftSpi and | |||||
| * SdFatLibSpi classes. SdFatSoftSpi uses software SPI and SdFatLibSpi | |||||
| * uses the standard Arduino SPI library. | |||||
| */ | |||||
| #define USE_MULTIPLE_SPI_TYPES 0 | |||||
| You can also edit other configuration options in SdFatConfig.h to | |||||
| change the type of SPI used by the SdFat class. |
| Relative paths in SdFat are resolved in a manner similar to Windows. | |||||
| Each instance of SdFat has a current directory. In SdFat this directory | |||||
| is called the volume working directory, vwd. Initially this directory is | |||||
| the root directory for the volume. | |||||
| The volume working directory is changed by calling SdFat::chdir(path). | |||||
| The call sd.chdir("/2011") will change the volume working directory | |||||
| for sd to "/2011", assuming "/2011" exists. | |||||
| Relative paths for SdFat member functions are resolved by starting at | |||||
| the volume working directory. | |||||
| For example, the call sd.mkdir("APRIL") will create the directory | |||||
| "/2011/APRIL" assuming the volume working directory is "/2011". | |||||
| SdFat has a current working directory, cwd, that is used to resolve paths | |||||
| for file.open() calls. | |||||
| For a single SD card the current working directory is always the volume | |||||
| working directory for that card. | |||||
| For multiple SD cards the current working directory is set to the volume | |||||
| working directory of a card by calling the SdFat::chvol() member function. | |||||
| The chvol() call is like the Windows <drive letter>: command. | |||||
| The call sd2.chvol() will set the current working directory to the volume | |||||
| working directory for sd2. | |||||
| If the volume working directory for sd2 is "/MUSIC" the call | |||||
| file.open("BIGBAND.WAV", O_READ); | |||||
| will then open "/MUSIC/BIGBAND.WAV" on sd2. | |||||
| The following functions are used to change or get current directories. | |||||
| See the html documentation for more information. | |||||
| bool SdFat::chdir(bool set_cwd = false); | |||||
| bool SdFat::chdir(const char* path, bool set_cwd = false); | |||||
| void SdFat::chvol(); | |||||
| SdBaseFile* SdFat::vwd(); | |||||
| static SdBaseFile* SdBaseFile::cwd(); |
| 04 Dec 2014 | |||||
| Added support for Long File Names. | |||||
| 14 Nov 2014 | 14 Nov 2014 | ||||
| Replaced the core SdFat code with FatLib, a generic FAT12/FAT16/FAT32 | Replaced the core SdFat code with FatLib, a generic FAT12/FAT16/FAT32 |
| <div class="dyncontent"> | <div class="dyncontent"> | ||||
| <div class="center"><img src="_arduino_stream_8h__incl.png" border="0" usemap="#_arduino_2libraries_2_sd_fat_2utility_2_arduino_stream_8h" alt=""/></div> | <div class="center"><img src="_arduino_stream_8h__incl.png" border="0" usemap="#_arduino_2libraries_2_sd_fat_2utility_2_arduino_stream_8h" alt=""/></div> | ||||
| <map name="_arduino_2libraries_2_sd_fat_2utility_2_arduino_stream_8h" id="_arduino_2libraries_2_sd_fat_2utility_2_arduino_stream_8h"> | <map name="_arduino_2libraries_2_sd_fat_2utility_2_arduino_stream_8h" id="_arduino_2libraries_2_sd_fat_2utility_2_arduino_stream_8h"> | ||||
| <area shape="rect" id="node5" href="bufstream_8h.html" title="ibufstream and obufstream classes " alt="" coords="301,96,392,123"/> <area shape="rect" id="node9" href="iostream_8h.html" title="iostream class " alt="" coords="305,171,388,197"/> <area shape="rect" id="node11" href="istream_8h.html" title="istream class " alt="" coords="255,245,331,272"/> <area shape="rect" id="node38" href="ostream_8h.html" title="ostream class " alt="" coords="356,245,436,272"/> <area shape="rect" id="node13" href="ios_8h.html" title="ios_base and ios classes " alt="" coords="320,320,371,347"/> <area shape="rect" id="node15" href="_fat_file_8h.html" title="FatFile class. " alt="" coords="309,395,381,421"/> <area shape="rect" id="node24" href="_fat_lib_config_8h.html" title="configuration definitions " alt="" coords="425,544,529,571"/> <area shape="rect" id="node30" href="_fat_structs_8h.html" title="FAT file structures. " alt="" coords="115,544,209,571"/> <area shape="rect" id="node32" href="_fat_volume_8h.html" title="FatVolume class. " alt="" coords="157,469,253,496"/> <area shape="rect" id="node26" href="_sd_fat_config_8h.html" title="configuration definitions " alt="" coords="419,619,535,645"/> </map> | |||||
| <area shape="rect" id="node5" href="bufstream_8h.html" title="ibufstream and obufstream classes " alt="" coords="80,96,171,123"/> <area shape="rect" id="node9" href="iostream_8h.html" title="iostream class " alt="" coords="151,171,233,197"/> <area shape="rect" id="node11" href="istream_8h.html" title="istream class " alt="" coords="255,245,331,272"/> <area shape="rect" id="node36" href="ostream_8h.html" title="ostream class " alt="" coords="151,245,231,272"/> <area shape="rect" id="node13" href="ios_8h.html" title="ios_base and ios classes " alt="" coords="256,320,307,347"/> <area shape="rect" id="node15" href="_fat_file_8h.html" title="FatFile class. " alt="" coords="245,395,317,421"/> <area shape="rect" id="node22" href="_fat_lib_config_8h.html" title="configuration definitions " alt="" coords="427,544,532,571"/> <area shape="rect" id="node28" href="_fat_structs_8h.html" title="FAT file structures. " alt="" coords="179,544,273,571"/> <area shape="rect" id="node30" href="_fat_volume_8h.html" title="FatVolume class. " alt="" coords="221,469,317,496"/> <area shape="rect" id="node24" href="_sd_fat_config_8h.html" title="configuration definitions " alt="" coords="422,619,537,645"/> </map> | |||||
| </div> | </div> | ||||
| </div><table class="memberdecls"> | </div><table class="memberdecls"> | ||||
| <tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="nested-classes"></a> | <tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="nested-classes"></a> | ||||
| </div></div><!-- contents --> | </div></div><!-- contents --> | ||||
| <!-- start footer part --> | <!-- start footer part --> | ||||
| <hr class="footer"/><address class="footer"><small> | <hr class="footer"/><address class="footer"><small> | ||||
| Generated on Sat Nov 15 2014 10:05:35 for SdFat by  <a href="http://www.doxygen.org/index.html"> | |||||
| Generated on Thu Dec 4 2014 08:53:20 for SdFat by  <a href="http://www.doxygen.org/index.html"> | |||||
| <img class="footer" src="doxygen.png" alt="doxygen"/> | <img class="footer" src="doxygen.png" alt="doxygen"/> | ||||
| </a> 1.8.8 | </a> 1.8.8 | ||||
| </small></address> | </small></address> |
| </div></div><!-- contents --> | </div></div><!-- contents --> | ||||
| <!-- start footer part --> | <!-- start footer part --> | ||||
| <hr class="footer"/><address class="footer"><small> | <hr class="footer"/><address class="footer"><small> | ||||
| Generated on Sat Nov 15 2014 10:05:35 for SdFat by  <a href="http://www.doxygen.org/index.html"> | |||||
| Generated on Thu Dec 4 2014 08:53:20 for SdFat by  <a href="http://www.doxygen.org/index.html"> | |||||
| <img class="footer" src="doxygen.png" alt="doxygen"/> | <img class="footer" src="doxygen.png" alt="doxygen"/> | ||||
| </a> 1.8.8 | </a> 1.8.8 | ||||
| </small></address> | </small></address> |
| <div class="summary"> | <div class="summary"> | ||||
| <a href="#nested-classes">Classes</a> | | <a href="#nested-classes">Classes</a> | | ||||
| <a href="#define-members">Macros</a> | | <a href="#define-members">Macros</a> | | ||||
| <a href="#typedef-members">Typedefs</a> </div> | |||||
| <a href="#var-members">Variables</a> </div> | |||||
| <div class="headertitle"> | <div class="headertitle"> | ||||
| <div class="title">FatFile.h File Reference</div> </div> | <div class="title">FatFile.h File Reference</div> </div> | ||||
| </div><!--header--> | </div><!--header--> | ||||
| <p><a class="el" href="class_fat_file.html" title="Basic file class. ">FatFile</a> class. | <p><a class="el" href="class_fat_file.html" title="Basic file class. ">FatFile</a> class. | ||||
| <a href="#details">More...</a></p> | <a href="#details">More...</a></p> | ||||
| <div class="textblock"><code>#include <ctype.h></code><br /> | |||||
| <code>#include <string.h></code><br /> | |||||
| <div class="textblock"><code>#include <string.h></code><br /> | |||||
| <code>#include <stddef.h></code><br /> | <code>#include <stddef.h></code><br /> | ||||
| <code>#include <limits.h></code><br /> | <code>#include <limits.h></code><br /> | ||||
| <code>#include "<a class="el" href="_fat_lib_config_8h.html">FatLibConfig.h</a>"</code><br /> | <code>#include "<a class="el" href="_fat_lib_config_8h.html">FatLibConfig.h</a>"</code><br /> | ||||
| <code>#include "FatApiConstants.h"</code><br /> | <code>#include "FatApiConstants.h"</code><br /> | ||||
| <code>#include "<a class="el" href="_fat_structs_8h.html">FatStructs.h</a>"</code><br /> | <code>#include "<a class="el" href="_fat_structs_8h.html">FatStructs.h</a>"</code><br /> | ||||
| <code>#include "<a class="el" href="_fat_volume_8h.html">FatVolume.h</a>"</code><br /> | <code>#include "<a class="el" href="_fat_volume_8h.html">FatVolume.h</a>"</code><br /> | ||||
| <code>#include <Arduino.h></code><br /> | |||||
| </div><div class="textblock"><div class="dynheader"> | </div><div class="textblock"><div class="dynheader"> | ||||
| Include dependency graph for FatFile.h:</div> | Include dependency graph for FatFile.h:</div> | ||||
| <div class="dyncontent"> | <div class="dyncontent"> | ||||
| <div class="center"><img src="_fat_file_8h__incl.png" border="0" usemap="#_arduino_2libraries_2_sd_fat_2utility_2_fat_file_8h" alt=""/></div> | <div class="center"><img src="_fat_file_8h__incl.png" border="0" usemap="#_arduino_2libraries_2_sd_fat_2utility_2_fat_file_8h" alt=""/></div> | ||||
| <map name="_arduino_2libraries_2_sd_fat_2utility_2_fat_file_8h" id="_arduino_2libraries_2_sd_fat_2utility_2_fat_file_8h"> | <map name="_arduino_2libraries_2_sd_fat_2utility_2_fat_file_8h" id="_arduino_2libraries_2_sd_fat_2utility_2_fat_file_8h"> | ||||
| <area shape="rect" id="node11" href="_fat_lib_config_8h.html" title="configuration definitions " alt="" coords="249,171,353,197"/> <area shape="rect" id="node17" href="_fat_structs_8h.html" title="FAT file structures. " alt="" coords="378,171,472,197"/> <area shape="rect" id="node19" href="_fat_volume_8h.html" title="FatVolume class. " alt="" coords="235,96,331,123"/> <area shape="rect" id="node13" href="_sd_fat_config_8h.html" title="configuration definitions " alt="" coords="243,245,359,272"/> </map> | |||||
| <area shape="rect" id="node9" href="_fat_lib_config_8h.html" title="configuration definitions " alt="" coords="106,171,211,197"/> <area shape="rect" id="node15" href="_fat_structs_8h.html" title="FAT file structures. " alt="" coords="338,171,432,197"/> <area shape="rect" id="node17" href="_fat_volume_8h.html" title="FatVolume class. " alt="" coords="196,96,292,123"/> <area shape="rect" id="node11" href="_sd_fat_config_8h.html" title="configuration definitions " alt="" coords="101,245,216,272"/> </map> | |||||
| </div> | </div> | ||||
| </div><div class="textblock"><div class="dynheader"> | </div><div class="textblock"><div class="dynheader"> | ||||
| This graph shows which files directly or indirectly include this file:</div> | This graph shows which files directly or indirectly include this file:</div> | ||||
| <tr class="memitem:"><td class="memItemLeft" align="right" valign="top">struct  </td><td class="memItemRight" valign="bottom"><a class="el" href="struct_fat_pos__t.html">FatPos_t</a></td></tr> | <tr class="memitem:"><td class="memItemLeft" align="right" valign="top">struct  </td><td class="memItemRight" valign="bottom"><a class="el" href="struct_fat_pos__t.html">FatPos_t</a></td></tr> | ||||
| <tr class="memdesc:"><td class="mdescLeft"> </td><td class="mdescRight">Internal type for file position - do not use in user apps. <a href="struct_fat_pos__t.html#details">More...</a><br /></td></tr> | <tr class="memdesc:"><td class="mdescLeft"> </td><td class="mdescRight">Internal type for file position - do not use in user apps. <a href="struct_fat_pos__t.html#details">More...</a><br /></td></tr> | ||||
| <tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr> | <tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr> | ||||
| <tr class="memitem:"><td class="memItemLeft" align="right" valign="top">struct  </td><td class="memItemRight" valign="bottom"><a class="el" href="structfname__t.html">fname_t</a></td></tr> | |||||
| <tr class="memdesc:"><td class="mdescLeft"> </td><td class="mdescRight">Internal type for Short <a class="el" href="class_file.html" title="Arduino SD.h style File API. ">File</a> Name - do not use in user apps. <a href="structfname__t.html#details">More...</a><br /></td></tr> | |||||
| <tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr> | |||||
| </table><table class="memberdecls"> | </table><table class="memberdecls"> | ||||
| <tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="define-members"></a> | <tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="define-members"></a> | ||||
| Macros</h2></td></tr> | Macros</h2></td></tr> | ||||
| <tr class="memitem:a9f85580ad6f1dfc86fff09a58ff0a1c0"><td class="memItemLeft" align="right" valign="top">#define </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#a9f85580ad6f1dfc86fff09a58ff0a1c0">isDirSeparator</a>(c)   ((c) == '/')</td></tr> | |||||
| <tr class="separator:a9f85580ad6f1dfc86fff09a58ff0a1c0"><td class="memSeparator" colspan="2"> </td></tr> | |||||
| <tr class="memitem:a963f816fc88a5d8479c285ed4c630229"><td class="memItemLeft" align="right" valign="top">#define </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#a963f816fc88a5d8479c285ed4c630229">PGM_P</a>   const char*</td></tr> | <tr class="memitem:a963f816fc88a5d8479c285ed4c630229"><td class="memItemLeft" align="right" valign="top">#define </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#a963f816fc88a5d8479c285ed4c630229">PGM_P</a>   const char*</td></tr> | ||||
| <tr class="separator:a963f816fc88a5d8479c285ed4c630229"><td class="memSeparator" colspan="2"> </td></tr> | <tr class="separator:a963f816fc88a5d8479c285ed4c630229"><td class="memSeparator" colspan="2"> </td></tr> | ||||
| <tr class="memitem:a48c60b057902adf805797f183286728d"><td class="memItemLeft" align="right" valign="top">#define </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#a48c60b057902adf805797f183286728d">pgm_read_byte</a>(addr)   (*(const unsigned char*)(addr))</td></tr> | <tr class="memitem:a48c60b057902adf805797f183286728d"><td class="memItemLeft" align="right" valign="top">#define </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#a48c60b057902adf805797f183286728d">pgm_read_byte</a>(addr)   (*(const unsigned char*)(addr))</td></tr> | ||||
| <tr class="memitem:a9c00057fd19e916cc1aa0a5949336beb"><td class="memItemLeft" align="right" valign="top">#define </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#a9c00057fd19e916cc1aa0a5949336beb">PSTR</a>(x)   (x)</td></tr> | <tr class="memitem:a9c00057fd19e916cc1aa0a5949336beb"><td class="memItemLeft" align="right" valign="top">#define </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#a9c00057fd19e916cc1aa0a5949336beb">PSTR</a>(x)   (x)</td></tr> | ||||
| <tr class="separator:a9c00057fd19e916cc1aa0a5949336beb"><td class="memSeparator" colspan="2"> </td></tr> | <tr class="separator:a9c00057fd19e916cc1aa0a5949336beb"><td class="memSeparator" colspan="2"> </td></tr> | ||||
| </table><table class="memberdecls"> | </table><table class="memberdecls"> | ||||
| <tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="typedef-members"></a> | |||||
| Typedefs</h2></td></tr> | |||||
| <tr class="memitem:ac62f6449331cfe1a71f29be30efe7890"><td class="memItemLeft" align="right" valign="top">typedef Print </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#ac62f6449331cfe1a71f29be30efe7890">print_t</a></td></tr> | |||||
| <tr class="separator:ac62f6449331cfe1a71f29be30efe7890"><td class="memSeparator" colspan="2"> </td></tr> | |||||
| <tr class="heading"><td colspan="2"><h2 class="groupheader"><a name="var-members"></a> | |||||
| Variables</h2></td></tr> | |||||
| <tr class="memitem:a79e43960e1b4eecf274f5faea9c3168c"><td class="memItemLeft" align="right" valign="top">const uint8_t </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#a79e43960e1b4eecf274f5faea9c3168c">FNAME_FLAG_LC_BASE</a> = <a class="el" href="_fat_structs_8h.html#a39f9b8960dba007b537e9b71c25384fe">DIR_NT_LC_BASE</a></td></tr> | |||||
| <tr class="separator:a79e43960e1b4eecf274f5faea9c3168c"><td class="memSeparator" colspan="2"> </td></tr> | |||||
| <tr class="memitem:a135b7572768b09661aa38afaceec7296"><td class="memItemLeft" align="right" valign="top">const uint8_t </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#a135b7572768b09661aa38afaceec7296">FNAME_FLAG_LC_EXT</a> = <a class="el" href="_fat_structs_8h.html#a8766a8bbab6ad3da38c1b308545d7572">DIR_NT_LC_EXT</a></td></tr> | |||||
| <tr class="separator:a135b7572768b09661aa38afaceec7296"><td class="memSeparator" colspan="2"> </td></tr> | |||||
| <tr class="memitem:acd45286b7dfc5ba68be18c8c3a9d298d"><td class="memItemLeft" align="right" valign="top">const uint8_t </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#acd45286b7dfc5ba68be18c8c3a9d298d">FNAME_FLAG_LOST_CHARS</a> = 0X01</td></tr> | |||||
| <tr class="separator:acd45286b7dfc5ba68be18c8c3a9d298d"><td class="memSeparator" colspan="2"> </td></tr> | |||||
| <tr class="memitem:a63994c21f3b723a55247f063a1b01c9c"><td class="memItemLeft" align="right" valign="top">const uint8_t </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#a63994c21f3b723a55247f063a1b01c9c">FNAME_FLAG_MIXED_CASE</a> = 0X02</td></tr> | |||||
| <tr class="separator:a63994c21f3b723a55247f063a1b01c9c"><td class="memSeparator" colspan="2"> </td></tr> | |||||
| <tr class="memitem:a1a041207a19d2fd9a1e2739343ccb29b"><td class="memItemLeft" align="right" valign="top">const uint8_t </td><td class="memItemRight" valign="bottom"><a class="el" href="_fat_file_8h.html#a1a041207a19d2fd9a1e2739343ccb29b">FNAME_FLAG_NEED_LFN</a></td></tr> | |||||
| <tr class="separator:a1a041207a19d2fd9a1e2739343ccb29b"><td class="memSeparator" colspan="2"> </td></tr> | |||||
| </table> | </table> | ||||
| <a name="details" id="details"></a><h2 class="groupheader">Detailed Description</h2> | <a name="details" id="details"></a><h2 class="groupheader">Detailed Description</h2> | ||||
| <div class="textblock"><p><a class="el" href="class_fat_file.html" title="Basic file class. ">FatFile</a> class. </p> | <div class="textblock"><p><a class="el" href="class_fat_file.html" title="Basic file class. ">FatFile</a> class. </p> | ||||
| </div><h2 class="groupheader">Macro Definition Documentation</h2> | </div><h2 class="groupheader">Macro Definition Documentation</h2> | ||||
| <a class="anchor" id="a9f85580ad6f1dfc86fff09a58ff0a1c0"></a> | |||||
| <div class="memitem"> | |||||
| <div class="memproto"> | |||||
| <table class="memname"> | |||||
| <tr> | |||||
| <td class="memname">#define isDirSeparator</td> | |||||
| <td>(</td> | |||||
| <td class="paramtype"> </td> | |||||
| <td class="paramname">c</td><td>)</td> | |||||
| <td>   ((c) == '/')</td> | |||||
| </tr> | |||||
| </table> | |||||
| </div><div class="memdoc"> | |||||
| <p>Expression for path name separator. </p> | |||||
| </div> | |||||
| </div> | |||||
| <a class="anchor" id="a963f816fc88a5d8479c285ed4c630229"></a> | <a class="anchor" id="a963f816fc88a5d8479c285ed4c630229"></a> | ||||
| <div class="memitem"> | <div class="memitem"> | ||||
| <div class="memproto"> | <div class="memproto"> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <h2 class="groupheader">Typedef Documentation</h2> | |||||
| <a class="anchor" id="ac62f6449331cfe1a71f29be30efe7890"></a> | |||||
| <h2 class="groupheader">Variable Documentation</h2> | |||||
| <a class="anchor" id="a79e43960e1b4eecf274f5faea9c3168c"></a> | |||||
| <div class="memitem"> | |||||
| <div class="memproto"> | |||||
| <table class="memname"> | |||||
| <tr> | |||||
| <td class="memname">const uint8_t FNAME_FLAG_LC_BASE = <a class="el" href="_fat_structs_8h.html#a39f9b8960dba007b537e9b71c25384fe">DIR_NT_LC_BASE</a></td> | |||||
| </tr> | |||||
| </table> | |||||
| </div><div class="memdoc"> | |||||
| <p>Filename base-name is all lower case </p> | |||||
| </div> | |||||
| </div> | |||||
| <a class="anchor" id="a135b7572768b09661aa38afaceec7296"></a> | |||||
| <div class="memitem"> | |||||
| <div class="memproto"> | |||||
| <table class="memname"> | |||||
| <tr> | |||||
| <td class="memname">const uint8_t FNAME_FLAG_LC_EXT = <a class="el" href="_fat_structs_8h.html#a8766a8bbab6ad3da38c1b308545d7572">DIR_NT_LC_EXT</a></td> | |||||
| </tr> | |||||
| </table> | |||||
| </div><div class="memdoc"> | |||||
| <p>Filename extension is all lower case. </p> | |||||
| </div> | |||||
| </div> | |||||
| <a class="anchor" id="acd45286b7dfc5ba68be18c8c3a9d298d"></a> | |||||
| <div class="memitem"> | |||||
| <div class="memproto"> | |||||
| <table class="memname"> | |||||
| <tr> | |||||
| <td class="memname">const uint8_t FNAME_FLAG_LOST_CHARS = 0X01</td> | |||||
| </tr> | |||||
| </table> | |||||
| </div><div class="memdoc"> | |||||
| <p>Derived from a LFN with loss or conversion of characters. </p> | |||||
| </div> | |||||
| </div> | |||||
| <a class="anchor" id="a63994c21f3b723a55247f063a1b01c9c"></a> | |||||
| <div class="memitem"> | |||||
| <div class="memproto"> | |||||
| <table class="memname"> | |||||
| <tr> | |||||
| <td class="memname">const uint8_t FNAME_FLAG_MIXED_CASE = 0X02</td> | |||||
| </tr> | |||||
| </table> | |||||
| </div><div class="memdoc"> | |||||
| <p>Base-name or extension has mixed case. </p> | |||||
| </div> | |||||
| </div> | |||||
| <a class="anchor" id="a1a041207a19d2fd9a1e2739343ccb29b"></a> | |||||
| <div class="memitem"> | <div class="memitem"> | ||||
| <div class="memproto"> | <div class="memproto"> | ||||
| <table class="memname"> | <table class="memname"> | ||||
| <tr> | <tr> | ||||
| <td class="memname">typedef Print <a class="el" href="_fat_file_8h.html#ac62f6449331cfe1a71f29be30efe7890">print_t</a></td> | |||||
| <td class="memname">const uint8_t FNAME_FLAG_NEED_LFN</td> | |||||
| </tr> | </tr> | ||||
| </table> | </table> | ||||
| </div><div class="memdoc"> | </div><div class="memdoc"> | ||||
| <p>Use Print on Arduino </p> | |||||
| <b>Initial value:</b><div class="fragment"><div class="line">=</div> | |||||
| <div class="line"> <a class="code" href="_fat_file_8h.html#acd45286b7dfc5ba68be18c8c3a9d298d">FNAME_FLAG_LOST_CHARS</a> | <a class="code" href="_fat_file_8h.html#a63994c21f3b723a55247f063a1b01c9c">FNAME_FLAG_MIXED_CASE</a></div> | |||||
| <div class="ttc" id="_fat_file_8h_html_a63994c21f3b723a55247f063a1b01c9c"><div class="ttname"><a href="_fat_file_8h.html#a63994c21f3b723a55247f063a1b01c9c">FNAME_FLAG_MIXED_CASE</a></div><div class="ttdeci">const uint8_t FNAME_FLAG_MIXED_CASE</div><div class="ttdef"><b>Definition:</b> FatFile.h:96</div></div> | |||||
| <div class="ttc" id="_fat_file_8h_html_acd45286b7dfc5ba68be18c8c3a9d298d"><div class="ttname"><a href="_fat_file_8h.html#acd45286b7dfc5ba68be18c8c3a9d298d">FNAME_FLAG_LOST_CHARS</a></div><div class="ttdeci">const uint8_t FNAME_FLAG_LOST_CHARS</div><div class="ttdef"><b>Definition:</b> FatFile.h:94</div></div> | |||||
| </div><!-- fragment --><p>LFN entries are required for file name. </p> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div><!-- contents --> | </div><!-- contents --> | ||||
| <!-- start footer part --> | <!-- start footer part --> | ||||
| <hr class="footer"/><address class="footer"><small> | <hr class="footer"/><address class="footer"><small> | ||||
| Generated on Sat Nov 15 2014 10:05:35 for SdFat by  <a href="http://www.doxygen.org/index.html"> | |||||
| Generated on Thu Dec 4 2014 08:53:20 for SdFat by  <a href="http://www.doxygen.org/index.html"> | |||||
| <img class="footer" src="doxygen.png" alt="doxygen"/> | <img class="footer" src="doxygen.png" alt="doxygen"/> | ||||
| </a> 1.8.8 | </a> 1.8.8 | ||||
| </small></address> | </small></address> |