From 82e3bf904f56ab57c6cb78f08c7e440c367a4239 Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Thu, 22 Aug 2019 14:23:52 -0400 Subject: [PATCH 1/2] Add File::availableForWrite API --- src/File.cpp | 7 +++++ src/SD.h | 1 + src/utility/Sd2Card.cpp | 37 +++++++++++++++++-------- src/utility/Sd2Card.h | 3 ++- src/utility/SdFat.h | 22 ++++++++++----- src/utility/SdFile.cpp | 58 +++++++++++++++++++++++++++++++++++++--- src/utility/SdVolume.cpp | 26 +++++++++++++----- 7 files changed, 126 insertions(+), 28 deletions(-) diff --git a/src/File.cpp b/src/File.cpp index c4af48d..5e37166 100644 --- a/src/File.cpp +++ b/src/File.cpp @@ -73,6 +73,13 @@ size_t File::write(const uint8_t *buf, size_t size) { return t; } +int File::availableForWrite() { + if (_file) { + return _file->availableForWrite(); + } + return 0; +} + int File::peek() { if (! _file) { return 0; diff --git a/src/SD.h b/src/SD.h index bb81fb4..fa34ccd 100644 --- a/src/SD.h +++ b/src/SD.h @@ -35,6 +35,7 @@ namespace SDLib { File(void); // 'empty' constructor virtual size_t write(uint8_t); virtual size_t write(const uint8_t *buf, size_t size); + virtual int availableForWrite(); virtual int read(); virtual int peek(); virtual int available(); diff --git a/src/utility/Sd2Card.cpp b/src/utility/Sd2Card.cpp index 586a92b..7cfbe73 100644 --- a/src/utility/Sd2Card.cpp +++ b/src/utility/Sd2Card.cpp @@ -608,10 +608,11 @@ uint8_t Sd2Card::waitStartBlock(void) { \param[in] blockNumber Logical block to be written. \param[in] src Pointer to the location of the data to be written. + \param[in] blocking If the write should be blocking. \return The value one, true, is returned for success and the value zero, false, is returned for failure. */ -uint8_t Sd2Card::writeBlock(uint32_t blockNumber, const uint8_t* src) { +uint8_t Sd2Card::writeBlock(uint32_t blockNumber, const uint8_t* src, uint8_t blocking) { #if SD_PROTECT_BLOCK_ZERO // don't allow write to first block if (blockNumber == 0) { @@ -631,16 +632,17 @@ uint8_t Sd2Card::writeBlock(uint32_t blockNumber, const uint8_t* src) { if (!writeData(DATA_START_BLOCK, src)) { goto fail; } - - // wait for flash programming to complete - if (!waitNotBusy(SD_WRITE_TIMEOUT)) { - error(SD_CARD_ERROR_WRITE_TIMEOUT); - goto fail; - } - // response is r2 so get and check two bytes for nonzero - if (cardCommand(CMD13, 0) || spiRec()) { - error(SD_CARD_ERROR_WRITE_PROGRAMMING); - goto fail; + if (blocking) { + // wait for flash programming to complete + if (!waitNotBusy(SD_WRITE_TIMEOUT)) { + error(SD_CARD_ERROR_WRITE_TIMEOUT); + goto fail; + } + // response is r2 so get and check two bytes for nonzero + if (cardCommand(CMD13, 0) || spiRec()) { + error(SD_CARD_ERROR_WRITE_PROGRAMMING); + goto fail; + } } chipSelectHigh(); return true; @@ -760,3 +762,16 @@ uint8_t Sd2Card::writeStop(void) { chipSelectHigh(); return false; } +//------------------------------------------------------------------------------ +/** Check if the SD card is busy + + \return The value one, true, is returned when is busy and + the value zero, false, is returned for when is NOT busy. +*/ +uint8_t Sd2Card::isBusy(void) { + chipSelectLow(); + byte b = spiRec(); + chipSelectHigh(); + + return (b != 0XFF); +} diff --git a/src/utility/Sd2Card.h b/src/utility/Sd2Card.h index d7520be..5d91ebf 100644 --- a/src/utility/Sd2Card.h +++ b/src/utility/Sd2Card.h @@ -236,10 +236,11 @@ class Sd2Card { uint8_t type(void) const { return type_; } - uint8_t writeBlock(uint32_t blockNumber, const uint8_t* src); + uint8_t writeBlock(uint32_t blockNumber, const uint8_t* src, uint8_t blocking = 1); uint8_t writeData(const uint8_t* src); uint8_t writeStart(uint32_t blockNumber, uint32_t eraseCount); uint8_t writeStop(void); + uint8_t isBusy(void); private: uint32_t block_; uint8_t chipSelectPin_; diff --git a/src/utility/SdFat.h b/src/utility/SdFat.h index acad32e..109f941 100644 --- a/src/utility/SdFat.h +++ b/src/utility/SdFat.h @@ -295,7 +295,7 @@ class SdFile : public Print { } uint8_t timestamp(uint8_t flag, uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second); - uint8_t sync(void); + uint8_t sync(uint8_t blocking = 1); /** Type of this SdFile. You should use isFile() or isDir() instead of type() if possible. @@ -320,6 +320,7 @@ class SdFile : public Print { void write_P(PGM_P str); void writeln_P(PGM_P str); #endif + int availableForWrite(void); //------------------------------------------------------------------------------ #if ALLOW_DEPRECATED_FUNCTIONS // Deprecated functions - suppress cpplint warnings with NOLINT comment @@ -407,14 +408,16 @@ class SdFile : public Print { // should be 0XF static uint8_t const F_OFLAG = (O_ACCMODE | O_APPEND | O_SYNC); // available bits - static uint8_t const F_UNUSED = 0X30; + static uint8_t const F_FILE_NON_BLOCKING_WRITE = 0X10; + // a new cluster was added to the file + static uint8_t const F_FILE_CLUSTER_ADDED = 0X20; // use unbuffered SD read static uint8_t const F_FILE_UNBUFFERED_READ = 0X40; // sync of directory entry required static uint8_t const F_FILE_DIR_DIRTY = 0X80; // make sure F_OFLAG is ok - #if ((F_UNUSED | F_FILE_UNBUFFERED_READ | F_FILE_DIR_DIRTY) & F_OFLAG) + #if ((F_FILE_NON_BLOCKING_WRITE | F_FILE_CLUSTER_ADDED | F_FILE_UNBUFFERED_READ | F_FILE_DIR_DIRTY) & F_OFLAG) #error flags_ bits conflict #endif // flags_ bits @@ -587,7 +590,8 @@ class SdVolume { uint32_t blockNumber(uint32_t cluster, uint32_t position) const { return clusterStartBlock(cluster) + blockOfCluster(position); } - static uint8_t cacheFlush(void); + static uint8_t cacheFlush(uint8_t blocking = 1); + static uint8_t cacheMirrorBlockFlush(uint8_t blocking); static uint8_t cacheRawBlock(uint32_t blockNumber, uint8_t action); static void cacheSetDirty(void) { cacheDirty_ |= CACHE_FOR_WRITE; @@ -610,8 +614,14 @@ class SdVolume { uint16_t count, uint8_t* dst) { return sdCard_->readData(block, offset, count, dst); } - uint8_t writeBlock(uint32_t block, const uint8_t* dst) { - return sdCard_->writeBlock(block, dst); + uint8_t writeBlock(uint32_t block, const uint8_t* dst, uint8_t blocking = 1) { + return sdCard_->writeBlock(block, dst, blocking); + } + uint8_t isBusy(void) { + return sdCard_->isBusy(); + } + uint8_t isCacheMirrorBlockDirty(void) { + return (cacheMirrorBlock_ != 0); } }; #endif // SdFat_h diff --git a/src/utility/SdFile.cpp b/src/utility/SdFile.cpp index cac47d4..18a1db6 100644 --- a/src/utility/SdFile.cpp +++ b/src/utility/SdFile.cpp @@ -42,6 +42,7 @@ uint8_t SdFile::addCluster() { firstCluster_ = curCluster_; flags_ |= F_FILE_DIR_DIRTY; } + flags_ |= F_FILE_CLUSTER_ADDED; return true; } //------------------------------------------------------------------------------ @@ -1121,12 +1122,14 @@ uint8_t SdFile::seekSet(uint32_t pos) { The sync() call causes all modified data and directory fields to be written to the storage device. + \param[in] blocking If the sync should block until fully complete. + \return The value one, true, is returned for success and the value zero, false, is returned for failure. Reasons for failure include a call to sync() before a file has been opened or an I/O error. */ -uint8_t SdFile::sync(void) { +uint8_t SdFile::sync(uint8_t blocking) { // only allow open files and directories if (!isOpen()) { return false; @@ -1155,7 +1158,12 @@ uint8_t SdFile::sync(void) { // clear directory dirty flags_ &= ~F_FILE_DIR_DIRTY; } - return SdVolume::cacheFlush(); + + if (!blocking) { + flags_ &= ~F_FILE_NON_BLOCKING_WRITE; + } + + return SdVolume::cacheFlush(blocking); } //------------------------------------------------------------------------------ /** @@ -1325,6 +1333,8 @@ size_t SdFile::write(const void* buf, uint16_t nbyte) { // number of bytes left to write - must be before goto statements uint16_t nToWrite = nbyte; + // if blocking writes should be used + uint8_t blocking = (flags_ & F_FILE_NON_BLOCKING_WRITE) == 0x00; // error if not a normal file or is read-only if (!isFile() || !(flags_ & O_WRITE)) { @@ -1383,7 +1393,7 @@ size_t SdFile::write(const void* buf, uint16_t nbyte) { if (SdVolume::cacheBlockNumber_ == block) { SdVolume::cacheBlockNumber_ = 0XFFFFFFFF; } - if (!vol_->writeBlock(block, src)) { + if (!vol_->writeBlock(block, src, blocking)) { goto writeErrorReturn; } src += 512; @@ -1473,3 +1483,45 @@ void SdFile::writeln_P(PGM_P str) { println(); } #endif +//------------------------------------------------------------------------------ +/** + Check how many bytes can be written without blocking. + + \return The number of bytes that can be written without blocking. +*/ +int SdFile::availableForWrite() { + if (!isFile() || !(flags_ & O_WRITE)) { + return 0; + } + + // seek to end of file if append flag + if ((flags_ & O_APPEND) && curPosition_ != fileSize_) { + if (!seekEnd()) { + return 0; + } + } + + if (vol_->isBusy()) { + return 0; + } + + if (flags_ & F_FILE_CLUSTER_ADDED) { + // new cluster added, trigger a non-blocking sync + sync(0); + flags_ &= ~F_FILE_CLUSTER_ADDED; + return 0; + } + + if (vol_->isCacheMirrorBlockDirty()) { + // cache mirror block is dirty, trigger a non-blocking sync + vol_->cacheMirrorBlockFlush(0); + return 0; + } + + flags_ |= F_FILE_NON_BLOCKING_WRITE; + + uint16_t blockOffset = curPosition_ & 0X1FF; + uint16_t n = 512 - blockOffset; + + return n; +} diff --git a/src/utility/SdVolume.cpp b/src/utility/SdVolume.cpp index 30fa232..60375c4 100644 --- a/src/utility/SdVolume.cpp +++ b/src/utility/SdVolume.cpp @@ -108,23 +108,35 @@ uint8_t SdVolume::allocContiguous(uint32_t count, uint32_t* curCluster) { return true; } //------------------------------------------------------------------------------ -uint8_t SdVolume::cacheFlush(void) { +uint8_t SdVolume::cacheFlush(uint8_t blocking) { if (cacheDirty_) { - if (!sdCard_->writeBlock(cacheBlockNumber_, cacheBuffer_.data)) { + if (!sdCard_->writeBlock(cacheBlockNumber_, cacheBuffer_.data, blocking)) { return false; } + + if (!blocking) { + return true; + } + // mirror FAT tables - if (cacheMirrorBlock_) { - if (!sdCard_->writeBlock(cacheMirrorBlock_, cacheBuffer_.data)) { - return false; - } - cacheMirrorBlock_ = 0; + if (!cacheMirrorBlockFlush(blocking)) { + return false; } cacheDirty_ = 0; } return true; } //------------------------------------------------------------------------------ +uint8_t SdVolume::cacheMirrorBlockFlush(uint8_t blocking) { + if (cacheMirrorBlock_) { + if (!sdCard_->writeBlock(cacheMirrorBlock_, cacheBuffer_.data, blocking)) { + return false; + } + cacheMirrorBlock_ = 0; + } + return true; +} +//------------------------------------------------------------------------------ uint8_t SdVolume::cacheRawBlock(uint32_t blockNumber, uint8_t action) { if (cacheBlockNumber_ != blockNumber) { if (!cacheFlush()) { From 514986a516088ee6def6084ba41520fa1cba107c Mon Sep 17 00:00:00 2001 From: Sandeep Mistry Date: Fri, 23 Aug 2019 15:39:04 -0400 Subject: [PATCH 2/2] Add new Non-blocking write example --- .../NonBlockingWrite/NonBlockingWrite.ino | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 examples/NonBlockingWrite/NonBlockingWrite.ino diff --git a/examples/NonBlockingWrite/NonBlockingWrite.ino b/examples/NonBlockingWrite/NonBlockingWrite.ino new file mode 100644 index 0000000..29d8ec6 --- /dev/null +++ b/examples/NonBlockingWrite/NonBlockingWrite.ino @@ -0,0 +1,91 @@ +/* + Non-blocking Write + + This example demonstrates how to perform non-blocking writes + to a file on a SD card. The file will contain the current millis() + value every 10ms. If the SD card is busy, the data will be buffered + in order to not block the sketch. + + NOTE: myFile.availableForWrite() will automatically sync the + file contents as needed. You may lose some unsynced data + still if myFile.sync() or myFile.close() is not called. + + The circuit: + - Arduino MKR Zero board + - micro SD card attached + + This example code is in the public domain. +*/ + +#include + +// file name to use for writing +const char filename[] = "demo.txt"; + +// File object to represent file +File txtFile; + +// string to buffer output +String buffer; + +unsigned long lastMillis = 0; + +void setup() { + Serial.begin(9600); + while (!Serial); + + // reserve 1kB for String used as a buffer + buffer.reserve(1024); + + // set LED pin to output, used to blink when writing + pinMode(LED_BUILTIN, OUTPUT); + + // init the SD card + if (!SD.begin()) { + Serial.println("Card failed, or not present"); + // don't do anything more: + while (1); + } + + // If you want to start from an empty file, + // uncomment the next line: + // SD.remove(filename); + + // try to open the file for writing + txtFile = SD.open(filename, FILE_WRITE); + if (!txtFile) { + Serial.print("error opening "); + Serial.println(filename); + while (1); + } + + // add some new lines to start + txtFile.println(); + txtFile.println("Hello World!"); +} + +void loop() { + // check if it's been over 10 ms since the last line added + unsigned long now = millis(); + if ((now - lastMillis) >= 10) { + // add a new line to the buffer + buffer += "Hello "; + buffer += now; + buffer += "\r\n"; + + lastMillis = now; + } + + // check if the SD card is available to write data without blocking + // and if the buffered data is enough for the full chunk size + unsigned int chunkSize = txtFile.availableForWrite(); + if (chunkSize && buffer.length() >= chunkSize) { + // write to file and blink LED + digitalWrite(LED_BUILTIN, HIGH); + txtFile.write(buffer.c_str(), chunkSize); + digitalWrite(LED_BUILTIN, LOW); + + // remove written data from buffer + buffer.remove(0, chunkSize); + } +}