Skip to content

Add File::availableForWrite API #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions examples/NonBlockingWrite/NonBlockingWrite.ino
Original file line number Diff line number Diff line change
@@ -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 <SD.h>

// 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);
}
}
7 changes: 7 additions & 0 deletions src/File.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/SD.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
37 changes: 26 additions & 11 deletions src/utility/Sd2Card.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
3 changes: 2 additions & 1 deletion src/utility/Sd2Card.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand Down
22 changes: 16 additions & 6 deletions src/utility/SdFat.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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;
Expand All @@ -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
58 changes: 55 additions & 3 deletions src/utility/SdFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ uint8_t SdFile::addCluster() {
firstCluster_ = curCluster_;
flags_ |= F_FILE_DIR_DIRTY;
}
flags_ |= F_FILE_CLUSTER_ADDED;
return true;
}
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
//------------------------------------------------------------------------------
/**
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
26 changes: 19 additions & 7 deletions src/utility/SdVolume.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down