parent
46f80e82d9
commit
64f2121ab1
@ -0,0 +1,1818 @@
|
|||||||
|
/* Arduino SdFat Library
|
||||||
|
* Copyright (C) 2009 by William Greiman
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This Library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with the Arduino SdFat Library. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include "SdBaseFile.h"
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// pointer to cwd directory
|
||||||
|
SdBaseFile* SdBaseFile::cwd_ = 0;
|
||||||
|
// callback function for date/time
|
||||||
|
void (*SdBaseFile::dateTime_)(uint16_t* date, uint16_t* time) = 0;
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// add a cluster to a file
|
||||||
|
bool SdBaseFile::addCluster() {
|
||||||
|
if (!vol_->allocContiguous(1, &curCluster_)) goto fail;
|
||||||
|
|
||||||
|
// if first cluster of file link to directory entry
|
||||||
|
if (firstCluster_ == 0) {
|
||||||
|
firstCluster_ = curCluster_;
|
||||||
|
flags_ |= F_FILE_DIR_DIRTY;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Add a cluster to a directory file and zero the cluster.
|
||||||
|
// return with first block of cluster in the cache
|
||||||
|
bool SdBaseFile::addDirCluster() {
|
||||||
|
uint32_t block;
|
||||||
|
// max folder size
|
||||||
|
if (fileSize_/sizeof(dir_t) >= 0XFFFF) goto fail;
|
||||||
|
|
||||||
|
if (!addCluster()) goto fail;
|
||||||
|
if (!vol_->cacheFlush()) goto fail;
|
||||||
|
|
||||||
|
block = vol_->clusterStartBlock(curCluster_);
|
||||||
|
|
||||||
|
// set cache to first block of cluster
|
||||||
|
vol_->cacheSetBlockNumber(block, true);
|
||||||
|
|
||||||
|
// zero first block of cluster
|
||||||
|
memset(vol_->cacheBuffer_.data, 0, 512);
|
||||||
|
|
||||||
|
// zero rest of cluster
|
||||||
|
for (uint8_t i = 1; i < vol_->blocksPerCluster_; i++) {
|
||||||
|
if (!vol_->writeBlock(block + i, vol_->cacheBuffer_.data)) goto fail;
|
||||||
|
}
|
||||||
|
// Increase directory file size by cluster size
|
||||||
|
fileSize_ += 512UL << vol_->clusterSizeShift_;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// cache a file's directory entry
|
||||||
|
// return pointer to cached entry or null for failure
|
||||||
|
dir_t* SdBaseFile::cacheDirEntry(uint8_t action) {
|
||||||
|
if (!vol_->cacheRawBlock(dirBlock_, action)) goto fail;
|
||||||
|
return vol_->cache()->dir + dirIndex_;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Close a file and force cached data and directory information
|
||||||
|
* to be written to the storage device.
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
* Reasons for failure include no file is open or an I/O error.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::close() {
|
||||||
|
bool rtn = sync();
|
||||||
|
type_ = FAT_FILE_TYPE_CLOSED;
|
||||||
|
return rtn;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Check for contiguous file and return its raw block range.
|
||||||
|
*
|
||||||
|
* \param[out] bgnBlock the first block address for the file.
|
||||||
|
* \param[out] endBlock the last block address for the file.
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
* Reasons for failure include file is not contiguous, file has zero length
|
||||||
|
* or an I/O error occurred.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock) {
|
||||||
|
// error if no blocks
|
||||||
|
if (firstCluster_ == 0) goto fail;
|
||||||
|
|
||||||
|
for (uint32_t c = firstCluster_; ; c++) {
|
||||||
|
uint32_t next;
|
||||||
|
if (!vol_->fatGet(c, &next)) goto fail;
|
||||||
|
|
||||||
|
// check for contiguous
|
||||||
|
if (next != (c + 1)) {
|
||||||
|
// error if not end of chain
|
||||||
|
if (!vol_->isEOC(next)) goto fail;
|
||||||
|
*bgnBlock = vol_->clusterStartBlock(firstCluster_);
|
||||||
|
*endBlock = vol_->clusterStartBlock(c)
|
||||||
|
+ vol_->blocksPerCluster_ - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Create and open a new contiguous file of a specified size.
|
||||||
|
*
|
||||||
|
* \note This function only supports short DOS 8.3 names.
|
||||||
|
* See open() for more information.
|
||||||
|
*
|
||||||
|
* \param[in] dirFile The directory where the file will be created.
|
||||||
|
* \param[in] path A path with a valid DOS 8.3 file name.
|
||||||
|
* \param[in] size The desired file size.
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
* Reasons for failure include \a 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::createContiguous(SdBaseFile* dirFile,
|
||||||
|
const char* path, uint32_t size) {
|
||||||
|
uint32_t count;
|
||||||
|
// don't allow zero length file
|
||||||
|
if (size == 0) goto fail;
|
||||||
|
if (!open(dirFile, path, O_CREAT | O_EXCL | O_RDWR)) goto fail;
|
||||||
|
|
||||||
|
// calculate number of clusters needed
|
||||||
|
count = ((size - 1) >> (vol_->clusterSizeShift_ + 9)) + 1;
|
||||||
|
|
||||||
|
// allocate clusters
|
||||||
|
if (!vol_->allocContiguous(count, &firstCluster_)) {
|
||||||
|
remove();
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
fileSize_ = size;
|
||||||
|
|
||||||
|
// insure sync() will update dir entry
|
||||||
|
flags_ |= F_FILE_DIR_DIRTY;
|
||||||
|
|
||||||
|
return sync();
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Return a 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.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::dirEntry(dir_t* dir) {
|
||||||
|
dir_t* p;
|
||||||
|
// make sure fields on SD are correct
|
||||||
|
if (!sync()) goto fail;
|
||||||
|
|
||||||
|
// read entry
|
||||||
|
p = cacheDirEntry(SdVolume::CACHE_FOR_READ);
|
||||||
|
if (!p) goto fail;
|
||||||
|
|
||||||
|
// copy to caller's struct
|
||||||
|
memcpy(dir, p, sizeof(dir_t));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Format the name field of \a dir into the 13 byte array
|
||||||
|
* \a name in standard 8.3 short name format.
|
||||||
|
*
|
||||||
|
* \param[in] dir The directory structure containing the name.
|
||||||
|
* \param[out] name A 13 byte char array for the formatted name.
|
||||||
|
*/
|
||||||
|
void SdBaseFile::dirName(const dir_t& dir, char* name) {
|
||||||
|
uint8_t j = 0;
|
||||||
|
for (uint8_t i = 0; i < 11; i++) {
|
||||||
|
if (dir.name[i] == ' ')continue;
|
||||||
|
if (i == 8) name[j++] = '.';
|
||||||
|
name[j++] = dir.name[i];
|
||||||
|
}
|
||||||
|
name[j] = 0;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Test for the existence of a file in a directory
|
||||||
|
*
|
||||||
|
* \param[in] name Name of the file to be tested for.
|
||||||
|
*
|
||||||
|
* The calling instance must be an open directory file.
|
||||||
|
*
|
||||||
|
* dirFile.exists("TOFIND.TXT") searches for "TOFIND.TXT" in the directory
|
||||||
|
* dirFile.
|
||||||
|
*
|
||||||
|
* \return true if the file exists else false.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::exists(const char* name) {
|
||||||
|
SdBaseFile file;
|
||||||
|
return file.open(this, name, O_READ);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Get a string from a file.
|
||||||
|
*
|
||||||
|
* fgets() reads bytes from a file into the array pointed to by \a str, until
|
||||||
|
* \a num - 1 bytes are read, or a delimiter is read and transferred to \a str,
|
||||||
|
* or end-of-file is encountered. The string is then terminated
|
||||||
|
* with a null byte.
|
||||||
|
*
|
||||||
|
* fgets() deletes CR, '\\r', from the string. This insures only a '\\n'
|
||||||
|
* terminates the string for Windows text files which use CRLF for newline.
|
||||||
|
*
|
||||||
|
* \param[out] str Pointer to the array where the string is stored.
|
||||||
|
* \param[in] num Maximum number of characters to be read
|
||||||
|
* (including the final null byte). Usually the length
|
||||||
|
* of the array \a str is used.
|
||||||
|
* \param[in] delim Optional set of delimiters. The default is "\n".
|
||||||
|
*
|
||||||
|
* \return For success fgets() returns the length of the string in \a str.
|
||||||
|
* If no data is read, fgets() returns zero for EOF or -1 if an error occurred.
|
||||||
|
**/
|
||||||
|
int16_t SdBaseFile::fgets(char* str, int16_t num, char* delim) {
|
||||||
|
char ch;
|
||||||
|
int16_t n = 0;
|
||||||
|
int16_t r = -1;
|
||||||
|
while ((n + 1) < num && (r = read(&ch, 1)) == 1) {
|
||||||
|
// delete CR
|
||||||
|
if (ch == '\r') continue;
|
||||||
|
str[n++] = ch;
|
||||||
|
if (!delim) {
|
||||||
|
if (ch == '\n') break;
|
||||||
|
} else {
|
||||||
|
if (strchr(delim, ch)) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (r < 0) {
|
||||||
|
// read error
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
str[n] = '\0';
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Get a file's name
|
||||||
|
*
|
||||||
|
* \param[out] name An array of 13 characters for the file's name.
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::getFilename(char* name) {
|
||||||
|
if (!isOpen()) return false;
|
||||||
|
|
||||||
|
if (isRoot()) {
|
||||||
|
name[0] = '/';
|
||||||
|
name[1] = '\0';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// cache entry
|
||||||
|
dir_t* p = cacheDirEntry(SdVolume::CACHE_FOR_READ);
|
||||||
|
if (!p) return false;
|
||||||
|
|
||||||
|
// format name
|
||||||
|
dirName(*p, name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
void SdBaseFile::getpos(fpos_t* pos) {
|
||||||
|
pos->position = curPosition_;
|
||||||
|
pos->cluster = curCluster_;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** List directory contents to Serial.
|
||||||
|
*
|
||||||
|
* \param[in] flags The inclusive OR of
|
||||||
|
*
|
||||||
|
* LS_DATE - %Print file modification date
|
||||||
|
*
|
||||||
|
* LS_SIZE - %Print file size.
|
||||||
|
*
|
||||||
|
* LS_R - Recursive list of subdirectories.
|
||||||
|
*/
|
||||||
|
void SdBaseFile::ls(uint8_t flags) {
|
||||||
|
ls(&Serial, flags, 0);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** List directory contents.
|
||||||
|
*
|
||||||
|
* \param[in] pr Print stream for list.
|
||||||
|
*
|
||||||
|
* \param[in] flags The inclusive OR of
|
||||||
|
*
|
||||||
|
* LS_DATE - %Print file modification date
|
||||||
|
*
|
||||||
|
* LS_SIZE - %Print file size.
|
||||||
|
*
|
||||||
|
* LS_R - Recursive list of subdirectories.
|
||||||
|
*
|
||||||
|
* \param[in] indent Amount of space before file name. Used for recursive
|
||||||
|
* list to indicate subdirectory level.
|
||||||
|
*/
|
||||||
|
void SdBaseFile::ls(Print* pr, uint8_t flags, uint8_t indent) {
|
||||||
|
rewind();
|
||||||
|
int8_t status;
|
||||||
|
while ((status = lsPrintNext(pr, flags, indent))) {
|
||||||
|
if (status > 1 && (flags & LS_R)) {
|
||||||
|
uint16_t index = curPosition()/32 - 1;
|
||||||
|
SdBaseFile s;
|
||||||
|
if (s.open(this, index, O_READ)) s.ls(pr, flags, indent + 2);
|
||||||
|
seekSet(32 * (index + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// saves 32 bytes on stack for ls recursion
|
||||||
|
// return 0 - EOF, 1 - normal file, or 2 - directory
|
||||||
|
int8_t SdBaseFile::lsPrintNext(Print *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(' ');
|
||||||
|
pr->print(dir.fileSize);
|
||||||
|
}
|
||||||
|
pr->println();
|
||||||
|
return DIR_IS_FILE(&dir) ? 1 : 2;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// format directory name field from a 8.3 name string
|
||||||
|
bool SdBaseFile::make83Name(const char* str, uint8_t* name, const char** ptr) {
|
||||||
|
uint8_t c;
|
||||||
|
uint8_t n = 7; // max index for part before dot
|
||||||
|
uint8_t i = 0;
|
||||||
|
// blank fill name and extension
|
||||||
|
while (i < 11) name[i++] = ' ';
|
||||||
|
i = 0;
|
||||||
|
while (*str != '\0' && *str != '/') {
|
||||||
|
c = *str++;
|
||||||
|
if (c == '.') {
|
||||||
|
if (n == 10) goto fail; // only one dot allowed
|
||||||
|
n = 10; // max index for full 8.3 name
|
||||||
|
i = 8; // place for extension
|
||||||
|
} else {
|
||||||
|
// illegal FAT characters
|
||||||
|
PGM_P p = PSTR("|<>^+=?/[];,*\"\\");
|
||||||
|
uint8_t b;
|
||||||
|
while ((b = pgm_read_byte(p++))) if (b == c) goto fail;
|
||||||
|
// check size and only allow ASCII printable characters
|
||||||
|
if (i > n || c < 0X21 || c > 0X7E)goto fail;
|
||||||
|
// only upper case allowed in 8.3 names - convert lower to upper
|
||||||
|
name[i++] = c < 'a' || c > 'z' ? c : c + ('A' - 'a');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*ptr = str;
|
||||||
|
// must have a file name, extension is optional
|
||||||
|
return name[0] != ' ';
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Make a new directory.
|
||||||
|
*
|
||||||
|
* \param[in] parent An open SdFat instance for the directory that will contain
|
||||||
|
* the new directory.
|
||||||
|
*
|
||||||
|
* \param[in] path A path with a valid 8.3 DOS name for the new directory.
|
||||||
|
*
|
||||||
|
* \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.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::mkdir(SdBaseFile* parent, const char* path, bool pFlag) {
|
||||||
|
uint8_t dname[11];
|
||||||
|
SdBaseFile dir1, dir2;
|
||||||
|
SdBaseFile* sub = &dir1;
|
||||||
|
SdBaseFile* start = parent;
|
||||||
|
|
||||||
|
if (!parent || isOpen()) goto fail;
|
||||||
|
|
||||||
|
if (*path == '/') {
|
||||||
|
while (*path == '/') path++;
|
||||||
|
if (!parent->isRoot()) {
|
||||||
|
if (!dir2.openRoot(parent->vol_)) goto fail;
|
||||||
|
parent = &dir2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (1) {
|
||||||
|
if (!make83Name(path, dname, &path)) goto fail;
|
||||||
|
while (*path == '/') path++;
|
||||||
|
if (!*path) break;
|
||||||
|
if (!sub->open(parent, dname, O_READ)) {
|
||||||
|
if (!pFlag || !sub->mkdir(parent, dname)) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parent != start) parent->close();
|
||||||
|
parent = sub;
|
||||||
|
sub = parent != &dir1 ? &dir1 : &dir2;
|
||||||
|
}
|
||||||
|
return mkdir(parent, dname);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
bool SdBaseFile::mkdir(SdBaseFile* parent, const uint8_t dname[11]) {
|
||||||
|
uint32_t block;
|
||||||
|
dir_t d;
|
||||||
|
dir_t* p;
|
||||||
|
|
||||||
|
if (!parent->isDir()) goto fail;
|
||||||
|
|
||||||
|
// create a normal file
|
||||||
|
if (!open(parent, dname, O_CREAT | O_EXCL | O_RDWR)) goto fail;
|
||||||
|
|
||||||
|
// convert file to directory
|
||||||
|
flags_ = O_READ;
|
||||||
|
type_ = FAT_FILE_TYPE_SUBDIR;
|
||||||
|
|
||||||
|
// allocate and zero first cluster
|
||||||
|
if (!addDirCluster())goto fail;
|
||||||
|
|
||||||
|
// force entry to SD
|
||||||
|
if (!sync()) goto fail;
|
||||||
|
|
||||||
|
// cache entry - should already be in cache due to sync() call
|
||||||
|
p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||||
|
if (!p) goto fail;
|
||||||
|
|
||||||
|
// change directory entry attribute
|
||||||
|
p->attributes = DIR_ATT_DIRECTORY;
|
||||||
|
|
||||||
|
// make entry for '.'
|
||||||
|
memcpy(&d, p, sizeof(d));
|
||||||
|
d.name[0] = '.';
|
||||||
|
for (uint8_t i = 1; i < 11; i++) d.name[i] = ' ';
|
||||||
|
|
||||||
|
// cache block for '.' and '..'
|
||||||
|
block = vol_->clusterStartBlock(firstCluster_);
|
||||||
|
if (!vol_->cacheRawBlock(block, SdVolume::CACHE_FOR_WRITE)) goto fail;
|
||||||
|
|
||||||
|
// copy '.' to block
|
||||||
|
memcpy(&vol_->cache()->dir[0], &d, sizeof(d));
|
||||||
|
|
||||||
|
// make entry for '..'
|
||||||
|
d.name[1] = '.';
|
||||||
|
if (parent->isRoot()) {
|
||||||
|
d.firstClusterLow = 0;
|
||||||
|
d.firstClusterHigh = 0;
|
||||||
|
} else {
|
||||||
|
d.firstClusterLow = parent->firstCluster_ & 0XFFFF;
|
||||||
|
d.firstClusterHigh = parent->firstCluster_ >> 16;
|
||||||
|
}
|
||||||
|
// copy '..' to block
|
||||||
|
memcpy(&vol_->cache()->dir[1], &d, sizeof(d));
|
||||||
|
|
||||||
|
// write first block
|
||||||
|
return vol_->cacheFlush();
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Open a file 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] oflag Values for \a oflag are constructed by a bitwise-inclusive
|
||||||
|
* OR of open flags. see SdBaseFile::open(SdBaseFile*, const char*, uint8_t).
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::open(const char* path, uint8_t oflag) {
|
||||||
|
return open(cwd_, path, oflag);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Open a file or directory by name.
|
||||||
|
*
|
||||||
|
* \param[in] dirFile An open SdFat instance for the directory containing the
|
||||||
|
* file to be opened.
|
||||||
|
*
|
||||||
|
* \param[in] path A path with a valid 8.3 DOS name for a file to be opened.
|
||||||
|
*
|
||||||
|
* \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
|
||||||
|
* OR of flags from the following list
|
||||||
|
*
|
||||||
|
* O_READ - Open for reading.
|
||||||
|
*
|
||||||
|
* O_RDONLY - Same as O_READ.
|
||||||
|
*
|
||||||
|
* O_WRITE - Open for writing.
|
||||||
|
*
|
||||||
|
* O_WRONLY - Same as O_WRITE.
|
||||||
|
*
|
||||||
|
* O_RDWR - Open for reading and writing.
|
||||||
|
*
|
||||||
|
* O_APPEND - If set, the file offset shall be set to the end of the
|
||||||
|
* file prior to each write.
|
||||||
|
*
|
||||||
|
* O_AT_END - Set the initial position at the end of the file.
|
||||||
|
*
|
||||||
|
* O_CREAT - If the file exists, this flag has no effect except as noted
|
||||||
|
* under O_EXCL below. Otherwise, the file shall be created
|
||||||
|
*
|
||||||
|
* O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file exists.
|
||||||
|
*
|
||||||
|
* O_SYNC - Call sync() after each write. This flag should not be used with
|
||||||
|
* write(uint8_t), write_P(PGM_P), writeln_P(PGM_P), or the Arduino Print class.
|
||||||
|
* These functions do character at a time writes so sync() will be called
|
||||||
|
* after each byte.
|
||||||
|
*
|
||||||
|
* O_TRUNC - If the file exists and is a regular file, and the file is
|
||||||
|
* successfully opened and is not read only, its length shall be truncated to 0.
|
||||||
|
*
|
||||||
|
* WARNING: A given file must not be opened by more than one SdBaseFile object
|
||||||
|
* of file corruption may occur.
|
||||||
|
*
|
||||||
|
* \note Directory files must be opened read only. Write and truncation is
|
||||||
|
* not allowed for directory files.
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
* Reasons for failure include this 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.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::open(SdBaseFile* dirFile, const char* path, uint8_t oflag) {
|
||||||
|
uint8_t dname[11];
|
||||||
|
SdBaseFile dir1, dir2;
|
||||||
|
SdBaseFile *parent = dirFile;
|
||||||
|
SdBaseFile *sub = &dir1;
|
||||||
|
|
||||||
|
if (!dirFile) goto fail;
|
||||||
|
|
||||||
|
// error if already open
|
||||||
|
if (isOpen()) goto fail;
|
||||||
|
|
||||||
|
if (*path == '/') {
|
||||||
|
while (*path == '/') path++;
|
||||||
|
if (!dirFile->isRoot()) {
|
||||||
|
if (!dir2.openRoot(dirFile->vol_)) goto fail;
|
||||||
|
parent = &dir2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (1) {
|
||||||
|
if (!make83Name(path, dname, &path)) goto fail;
|
||||||
|
while (*path == '/') path++;
|
||||||
|
if (!*path) break;
|
||||||
|
if (!sub->open(parent, dname, O_READ)) goto fail;
|
||||||
|
if (parent != dirFile) parent->close();
|
||||||
|
parent = sub;
|
||||||
|
sub = parent != &dir1 ? &dir1 : &dir2;
|
||||||
|
}
|
||||||
|
return open(parent, dname, oflag);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// open with filename in dname
|
||||||
|
bool SdBaseFile::open(SdBaseFile* dirFile,
|
||||||
|
const uint8_t dname[11], uint8_t oflag) {
|
||||||
|
bool emptyFound = false;
|
||||||
|
bool fileFound = false;
|
||||||
|
uint8_t index;
|
||||||
|
dir_t* p;
|
||||||
|
|
||||||
|
vol_ = dirFile->vol_;
|
||||||
|
|
||||||
|
dirFile->rewind();
|
||||||
|
// search for file
|
||||||
|
|
||||||
|
while (dirFile->curPosition_ < dirFile->fileSize_) {
|
||||||
|
index = 0XF & (dirFile->curPosition_ >> 5);
|
||||||
|
p = dirFile->readDirCache();
|
||||||
|
if (!p) goto fail;
|
||||||
|
|
||||||
|
if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) {
|
||||||
|
// remember first empty slot
|
||||||
|
if (!emptyFound) {
|
||||||
|
dirBlock_ = dirFile->vol_->cacheBlockNumber();
|
||||||
|
dirIndex_ = index;
|
||||||
|
emptyFound = true;
|
||||||
|
}
|
||||||
|
// done if no entries follow
|
||||||
|
if (p->name[0] == DIR_NAME_FREE) break;
|
||||||
|
} else if (!memcmp(dname, p->name, 11)) {
|
||||||
|
fileFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fileFound) {
|
||||||
|
// don't open existing file if O_EXCL
|
||||||
|
if (oflag & O_EXCL) goto fail;
|
||||||
|
} else {
|
||||||
|
// don't create unless O_CREAT and O_WRITE
|
||||||
|
if (!(oflag & O_CREAT) || !(oflag & O_WRITE)) goto fail;
|
||||||
|
if (emptyFound) {
|
||||||
|
index = dirIndex_;
|
||||||
|
p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||||
|
if (!p) goto fail;
|
||||||
|
} else {
|
||||||
|
if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) goto fail;
|
||||||
|
|
||||||
|
// add and zero cluster for dirFile - first cluster is in cache for write
|
||||||
|
if (!dirFile->addDirCluster()) goto fail;
|
||||||
|
|
||||||
|
// use first entry in cluster
|
||||||
|
p = dirFile->vol_->cache()->dir;
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
// initialize as empty file
|
||||||
|
memset(p, 0, sizeof(dir_t));
|
||||||
|
memcpy(p->name, dname, 11);
|
||||||
|
|
||||||
|
// set timestamps
|
||||||
|
if (dateTime_) {
|
||||||
|
// call user date/time function
|
||||||
|
dateTime_(&p->creationDate, &p->creationTime);
|
||||||
|
} else {
|
||||||
|
// use default date/time
|
||||||
|
p->creationDate = FAT_DEFAULT_DATE;
|
||||||
|
p->creationTime = FAT_DEFAULT_TIME;
|
||||||
|
}
|
||||||
|
p->lastAccessDate = p->creationDate;
|
||||||
|
p->lastWriteDate = p->creationDate;
|
||||||
|
p->lastWriteTime = p->creationTime;
|
||||||
|
|
||||||
|
// write entry to SD
|
||||||
|
if (!dirFile->vol_->cacheFlush()) goto fail;
|
||||||
|
}
|
||||||
|
// open entry in cache
|
||||||
|
return openCachedEntry(index, oflag);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Open a file by index.
|
||||||
|
*
|
||||||
|
* \param[in] dirFile An open SdFat instance for the directory.
|
||||||
|
*
|
||||||
|
* \param[in] index The \a index of the directory entry for the file to be
|
||||||
|
* opened. The value for \a index is (directory file position)/32.
|
||||||
|
*
|
||||||
|
* \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
|
||||||
|
* OR of flags O_READ, O_WRITE, O_TRUNC, and O_SYNC.
|
||||||
|
*
|
||||||
|
* See open() by path for definition of flags.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::open(SdBaseFile* dirFile, uint16_t index, uint8_t oflag) {
|
||||||
|
dir_t* p;
|
||||||
|
|
||||||
|
vol_ = dirFile->vol_;
|
||||||
|
|
||||||
|
// error if already open
|
||||||
|
if (isOpen() || !dirFile) goto fail;
|
||||||
|
|
||||||
|
// don't open existing file if O_EXCL - user call error
|
||||||
|
if (oflag & O_EXCL) goto fail;
|
||||||
|
|
||||||
|
// seek to location of entry
|
||||||
|
if (!dirFile->seekSet(32 * index)) goto fail;
|
||||||
|
|
||||||
|
// read entry into cache
|
||||||
|
p = dirFile->readDirCache();
|
||||||
|
if (!p) goto fail;
|
||||||
|
|
||||||
|
// error if empty slot or '.' or '..'
|
||||||
|
if (p->name[0] == DIR_NAME_FREE ||
|
||||||
|
p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
// open cached entry
|
||||||
|
return openCachedEntry(index & 0XF, oflag);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// open a cached directory entry. Assumes vol_ is initialized
|
||||||
|
bool SdBaseFile::openCachedEntry(uint8_t dirIndex, uint8_t oflag) {
|
||||||
|
// location of entry in cache
|
||||||
|
dir_t* p = &vol_->cache()->dir[dirIndex];
|
||||||
|
|
||||||
|
// write or truncate is an error for a directory or read-only file
|
||||||
|
if (p->attributes & (DIR_ATT_READ_ONLY | DIR_ATT_DIRECTORY)) {
|
||||||
|
if (oflag & (O_WRITE | O_TRUNC)) goto fail;
|
||||||
|
}
|
||||||
|
// remember location of directory entry on SD
|
||||||
|
dirBlock_ = vol_->cacheBlockNumber();
|
||||||
|
dirIndex_ = dirIndex;
|
||||||
|
|
||||||
|
// copy first cluster number for directory fields
|
||||||
|
firstCluster_ = (uint32_t)p->firstClusterHigh << 16;
|
||||||
|
firstCluster_ |= p->firstClusterLow;
|
||||||
|
|
||||||
|
// make sure it is a normal file or subdirectory
|
||||||
|
if (DIR_IS_FILE(p)) {
|
||||||
|
fileSize_ = p->fileSize;
|
||||||
|
type_ = FAT_FILE_TYPE_NORMAL;
|
||||||
|
} else if (DIR_IS_SUBDIR(p)) {
|
||||||
|
if (!vol_->chainSize(firstCluster_, &fileSize_)) goto fail;
|
||||||
|
type_ = FAT_FILE_TYPE_SUBDIR;
|
||||||
|
} else {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
// save open flags for read/write
|
||||||
|
flags_ = oflag & F_OFLAG;
|
||||||
|
|
||||||
|
// set to start of file
|
||||||
|
curCluster_ = 0;
|
||||||
|
curPosition_ = 0;
|
||||||
|
if ((oflag & O_TRUNC) && !truncate(0)) return false;
|
||||||
|
return oflag & O_AT_END ? seekEnd(0) : true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
type_ = FAT_FILE_TYPE_CLOSED;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Open the next file or subdirectory in a directory.
|
||||||
|
*
|
||||||
|
* \param[in] dirFile An open SdFat instance for the directory containing the
|
||||||
|
* file to be opened.
|
||||||
|
*
|
||||||
|
* \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
|
||||||
|
* OR of flags O_READ, O_WRITE, O_TRUNC, and O_SYNC.
|
||||||
|
*
|
||||||
|
* See open() by path for definition of flags.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::openNext(SdBaseFile* dirFile, uint8_t oflag) {
|
||||||
|
dir_t* p;
|
||||||
|
uint8_t index;
|
||||||
|
|
||||||
|
if (!dirFile) goto fail;
|
||||||
|
|
||||||
|
// error if already open
|
||||||
|
if (isOpen()) goto fail;
|
||||||
|
|
||||||
|
vol_ = dirFile->vol_;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
index = 0XF & (dirFile->curPosition_ >> 5);
|
||||||
|
|
||||||
|
// read entry into cache
|
||||||
|
p = dirFile->readDirCache();
|
||||||
|
if (!p) goto fail;
|
||||||
|
|
||||||
|
// done if last entry
|
||||||
|
if (p->name[0] == DIR_NAME_FREE) goto fail;
|
||||||
|
|
||||||
|
// skip empty slot or '.' or '..'
|
||||||
|
if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// must be file or dir
|
||||||
|
if (DIR_IS_FILE_OR_SUBDIR(p)) {
|
||||||
|
return openCachedEntry(index, oflag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Open a directory's parent directory.
|
||||||
|
*
|
||||||
|
* \param[in] dir Parent of this directory will be opened. Must not be root.
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::openParent(SdBaseFile* dir) {
|
||||||
|
dir_t entry;
|
||||||
|
dir_t* p;
|
||||||
|
SdBaseFile file;
|
||||||
|
uint32_t c;
|
||||||
|
uint32_t cluster;
|
||||||
|
uint32_t lbn;
|
||||||
|
// error if already open or dir is root or dir is not a directory
|
||||||
|
if (isOpen() || !dir || dir->isRoot() || !dir->isDir()) goto fail;
|
||||||
|
vol_ = dir->vol_;
|
||||||
|
// position to '..'
|
||||||
|
if (!dir->seekSet(32)) goto fail;
|
||||||
|
// read '..' entry
|
||||||
|
if (dir->read(&entry, sizeof(entry)) != 32) goto fail;
|
||||||
|
// verify it is '..'
|
||||||
|
if (entry.name[0] != '.' || entry.name[1] != '.') goto fail;
|
||||||
|
// start cluster for '..'
|
||||||
|
cluster = entry.firstClusterLow;
|
||||||
|
cluster |= (uint32_t)entry.firstClusterHigh << 16;
|
||||||
|
if (cluster == 0) return openRoot(vol_);
|
||||||
|
// start block for '..'
|
||||||
|
lbn = vol_->clusterStartBlock(cluster);
|
||||||
|
// first block of parent dir
|
||||||
|
if (!vol_->cacheRawBlock(lbn, SdVolume::CACHE_FOR_READ)) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
p = &vol_->cacheBuffer_.dir[1];
|
||||||
|
// verify name for '../..'
|
||||||
|
if (p->name[0] != '.' || p->name[1] != '.') goto fail;
|
||||||
|
// '..' is pointer to first cluster of parent. open '../..' to find parent
|
||||||
|
if (p->firstClusterHigh == 0 && p->firstClusterLow == 0) {
|
||||||
|
if (!file.openRoot(dir->volume())) goto fail;
|
||||||
|
} else {
|
||||||
|
if (!file.openCachedEntry(1, O_READ)) goto fail;
|
||||||
|
}
|
||||||
|
// search for parent in '../..'
|
||||||
|
do {
|
||||||
|
if (file.readDir(&entry) != 32) goto fail;
|
||||||
|
c = entry.firstClusterLow;
|
||||||
|
c |= (uint32_t)entry.firstClusterHigh << 16;
|
||||||
|
} while (c != cluster);
|
||||||
|
// open parent
|
||||||
|
return open(&file, file.curPosition()/32 - 1, O_READ);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Open a volume's root directory.
|
||||||
|
*
|
||||||
|
* \param[in] vol The FAT volume containing the root directory to be opened.
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
* Reasons for failure include the file is already open, the FAT volume has
|
||||||
|
* not been initialized or it a FAT12 volume.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::openRoot(SdVolume* vol) {
|
||||||
|
// error if file is already open
|
||||||
|
if (isOpen()) goto fail;
|
||||||
|
|
||||||
|
if (vol->fatType() == 16 || (FAT12_SUPPORT && vol->fatType() == 12)) {
|
||||||
|
type_ = FAT_FILE_TYPE_ROOT_FIXED;
|
||||||
|
firstCluster_ = 0;
|
||||||
|
fileSize_ = 32 * vol->rootDirEntryCount();
|
||||||
|
} else if (vol->fatType() == 32) {
|
||||||
|
type_ = FAT_FILE_TYPE_ROOT32;
|
||||||
|
firstCluster_ = vol->rootDirStart();
|
||||||
|
if (!vol->chainSize(firstCluster_, &fileSize_)) goto fail;
|
||||||
|
} else {
|
||||||
|
// volume is not initialized, invalid, or FAT12 without support
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vol_ = vol;
|
||||||
|
// read only
|
||||||
|
flags_ = O_READ;
|
||||||
|
|
||||||
|
// set to start of file
|
||||||
|
curCluster_ = 0;
|
||||||
|
curPosition_ = 0;
|
||||||
|
|
||||||
|
// root has no directory entry
|
||||||
|
dirBlock_ = 0;
|
||||||
|
dirIndex_ = 0;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Return the next available byte without consuming it.
|
||||||
|
*
|
||||||
|
* \return The byte if no error and not at eof else -1;
|
||||||
|
*/
|
||||||
|
int SdBaseFile::peek() {
|
||||||
|
fpos_t pos;
|
||||||
|
getpos(&pos);
|
||||||
|
int c = read();
|
||||||
|
if (c >= 0) setpos(&pos);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print the name field of a directory entry in 8.3 format to Serial.
|
||||||
|
*
|
||||||
|
* \param[in] dir The directory structure containing the name.
|
||||||
|
* \param[in] width Blank fill name if length is less than \a width.
|
||||||
|
* \param[in] printSlash Print '/' after directory names if true.
|
||||||
|
*/
|
||||||
|
void SdBaseFile::printDirName(const dir_t& dir,
|
||||||
|
uint8_t width, bool printSlash) {
|
||||||
|
printDirName(&Serial, dir, width, printSlash);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print the name field of a directory entry in 8.3 format.
|
||||||
|
* \param[in] pr Print stream for output.
|
||||||
|
* \param[in] dir The directory structure containing the name.
|
||||||
|
* \param[in] width Blank fill name if length is less than \a width.
|
||||||
|
* \param[in] printSlash Print '/' after directory names if true.
|
||||||
|
*/
|
||||||
|
void SdBaseFile::printDirName(Print* pr, const dir_t& dir,
|
||||||
|
uint8_t width, bool printSlash) {
|
||||||
|
uint8_t w = 0;
|
||||||
|
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) && printSlash) {
|
||||||
|
pr->write('/');
|
||||||
|
w++;
|
||||||
|
}
|
||||||
|
while (w < width) {
|
||||||
|
pr->write(' ');
|
||||||
|
w++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// print uint8_t with width 2
|
||||||
|
static void print2u(Print* pr, uint8_t v) {
|
||||||
|
if (v < 10) pr->write('0');
|
||||||
|
pr->print(v, DEC);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print a directory date field to Serial.
|
||||||
|
*
|
||||||
|
* Format is yyyy-mm-dd.
|
||||||
|
*
|
||||||
|
* \param[in] fatDate The date field from a directory entry.
|
||||||
|
*/
|
||||||
|
void SdBaseFile::printFatDate(uint16_t fatDate) {
|
||||||
|
printFatDate(&Serial, fatDate);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print a directory date field.
|
||||||
|
*
|
||||||
|
* Format is yyyy-mm-dd.
|
||||||
|
*
|
||||||
|
* \param[in] pr Print stream for output.
|
||||||
|
* \param[in] fatDate The date field from a directory entry.
|
||||||
|
*/
|
||||||
|
void SdBaseFile::printFatDate(Print* pr, uint16_t fatDate) {
|
||||||
|
pr->print(FAT_YEAR(fatDate));
|
||||||
|
pr->write('-');
|
||||||
|
print2u(pr, FAT_MONTH(fatDate));
|
||||||
|
pr->write('-');
|
||||||
|
print2u(pr, FAT_DAY(fatDate));
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print a directory time field to Serial.
|
||||||
|
*
|
||||||
|
* Format is hh:mm:ss.
|
||||||
|
*
|
||||||
|
* \param[in] fatTime The time field from a directory entry.
|
||||||
|
*/
|
||||||
|
void SdBaseFile::printFatTime(uint16_t fatTime) {
|
||||||
|
printFatTime(&Serial, fatTime);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print a directory time field.
|
||||||
|
*
|
||||||
|
* Format is hh:mm:ss.
|
||||||
|
*
|
||||||
|
* \param[in] pr Print stream for output.
|
||||||
|
* \param[in] fatTime The time field from a directory entry.
|
||||||
|
*/
|
||||||
|
void SdBaseFile::printFatTime(Print* pr, uint16_t fatTime) {
|
||||||
|
print2u(pr, FAT_HOUR(fatTime));
|
||||||
|
pr->write(':');
|
||||||
|
print2u(pr, FAT_MINUTE(fatTime));
|
||||||
|
pr->write(':');
|
||||||
|
print2u(pr, FAT_SECOND(fatTime));
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Print a file's name to Serial
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::printName() {
|
||||||
|
char name[13];
|
||||||
|
if (!getFilename(name)) return false;
|
||||||
|
Serial.print(name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Read the next byte from a file.
|
||||||
|
*
|
||||||
|
* \return For success read returns the next byte in the file as an int.
|
||||||
|
* If an error occurs or end of file is reached -1 is returned.
|
||||||
|
*/
|
||||||
|
int16_t SdBaseFile::read() {
|
||||||
|
uint8_t b;
|
||||||
|
return read(&b, 1) == 1 ? b : -1;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Read data from a file starting at the current position.
|
||||||
|
*
|
||||||
|
* \param[out] buf Pointer to the location that will receive the data.
|
||||||
|
*
|
||||||
|
* \param[in] nbyte Maximum number of bytes to read.
|
||||||
|
*
|
||||||
|
* \return For success read() returns the number of bytes read.
|
||||||
|
* A value less than \a nbyte, including zero, will be returned
|
||||||
|
* if end of file is reached.
|
||||||
|
* If an error occurs, read() returns -1. Possible errors include
|
||||||
|
* read() called before a file has been opened, corrupt file system
|
||||||
|
* or an I/O error occurred.
|
||||||
|
*/
|
||||||
|
int16_t SdBaseFile::read(void* buf, uint16_t nbyte) {
|
||||||
|
uint8_t* dst = reinterpret_cast<uint8_t*>(buf);
|
||||||
|
uint16_t offset;
|
||||||
|
uint16_t toRead;
|
||||||
|
uint32_t block; // raw device block number
|
||||||
|
|
||||||
|
// error if not open or write only
|
||||||
|
if (!isOpen() || !(flags_ & O_READ)) goto fail;
|
||||||
|
|
||||||
|
// max bytes left in file
|
||||||
|
if (nbyte >= (fileSize_ - curPosition_)) {
|
||||||
|
nbyte = fileSize_ - curPosition_;
|
||||||
|
}
|
||||||
|
// amount left to read
|
||||||
|
toRead = nbyte;
|
||||||
|
while (toRead > 0) {
|
||||||
|
offset = curPosition_ & 0X1FF; // offset in block
|
||||||
|
if (type_ == FAT_FILE_TYPE_ROOT_FIXED) {
|
||||||
|
block = vol_->rootDirStart() + (curPosition_ >> 9);
|
||||||
|
} else {
|
||||||
|
uint8_t blockOfCluster = vol_->blockOfCluster(curPosition_);
|
||||||
|
if (offset == 0 && blockOfCluster == 0) {
|
||||||
|
// start of new cluster
|
||||||
|
if (curPosition_ == 0) {
|
||||||
|
// use first cluster in file
|
||||||
|
curCluster_ = firstCluster_;
|
||||||
|
} else {
|
||||||
|
// get next cluster from FAT
|
||||||
|
if (!vol_->fatGet(curCluster_, &curCluster_)) goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block = vol_->clusterStartBlock(curCluster_) + blockOfCluster;
|
||||||
|
}
|
||||||
|
uint16_t n = toRead;
|
||||||
|
|
||||||
|
// amount to be read from current block
|
||||||
|
if (n > (512 - offset)) n = 512 - offset;
|
||||||
|
|
||||||
|
// no buffering needed if n == 512
|
||||||
|
if (n == 512 && block != vol_->cacheBlockNumber()) {
|
||||||
|
if (!vol_->readBlock(block, dst)) goto fail;
|
||||||
|
} else {
|
||||||
|
// read block to cache and copy data to caller
|
||||||
|
if (!vol_->cacheRawBlock(block, SdVolume::CACHE_FOR_READ)) goto fail;
|
||||||
|
uint8_t* src = vol_->cache()->data + offset;
|
||||||
|
memcpy(dst, src, n);
|
||||||
|
}
|
||||||
|
dst += n;
|
||||||
|
curPosition_ += n;
|
||||||
|
toRead -= n;
|
||||||
|
}
|
||||||
|
return nbyte;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Read the next directory entry from a directory file.
|
||||||
|
*
|
||||||
|
* \param[out] dir The dir_t struct that will receive the data.
|
||||||
|
*
|
||||||
|
* \return For success readDir() returns the number of bytes read.
|
||||||
|
* A value of zero will be returned if end of file is reached.
|
||||||
|
* If an error occurs, readDir() returns -1. Possible errors include
|
||||||
|
* readDir() called before a directory has been opened, this is not
|
||||||
|
* a directory file or an I/O error occurred.
|
||||||
|
*/
|
||||||
|
int8_t SdBaseFile::readDir(dir_t* dir) {
|
||||||
|
int16_t n;
|
||||||
|
// if not a directory file or miss-positioned return an error
|
||||||
|
if (!isDir() || (0X1F & curPosition_)) return -1;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
n = read(dir, sizeof(dir_t));
|
||||||
|
if (n != sizeof(dir_t)) return n == 0 ? 0 : -1;
|
||||||
|
// last entry if DIR_NAME_FREE
|
||||||
|
if (dir->name[0] == DIR_NAME_FREE) return 0;
|
||||||
|
// skip empty entries and entry for . and ..
|
||||||
|
if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') continue;
|
||||||
|
// return if normal file or subdirectory
|
||||||
|
if (DIR_IS_FILE_OR_SUBDIR(dir)) return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Read next directory entry into the cache
|
||||||
|
// Assumes file is correctly positioned
|
||||||
|
dir_t* SdBaseFile::readDirCache() {
|
||||||
|
uint8_t i;
|
||||||
|
// error if not directory
|
||||||
|
if (!isDir()) goto fail;
|
||||||
|
|
||||||
|
// index of entry in cache
|
||||||
|
i = (curPosition_ >> 5) & 0XF;
|
||||||
|
|
||||||
|
// use read to locate and cache block
|
||||||
|
if (read() < 0) goto fail;
|
||||||
|
|
||||||
|
// advance to next entry
|
||||||
|
curPosition_ += 31;
|
||||||
|
|
||||||
|
// return pointer to entry
|
||||||
|
return vol_->cache()->dir + i;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Remove a file.
|
||||||
|
*
|
||||||
|
* The directory entry and all data for the file are deleted.
|
||||||
|
*
|
||||||
|
* \note This function should not be used to delete the 8.3 version of a
|
||||||
|
* file that has a long name. For example if a file has the long name
|
||||||
|
* "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
* Reasons for failure include the file read-only, is a directory,
|
||||||
|
* or an I/O error occurred.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::remove() {
|
||||||
|
dir_t* d;
|
||||||
|
// free any clusters - will fail if read-only or directory
|
||||||
|
if (!truncate(0)) goto fail;
|
||||||
|
|
||||||
|
// cache directory entry
|
||||||
|
d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||||
|
if (!d) goto fail;
|
||||||
|
|
||||||
|
// mark entry deleted
|
||||||
|
d->name[0] = DIR_NAME_DELETED;
|
||||||
|
|
||||||
|
// set this file closed
|
||||||
|
type_ = FAT_FILE_TYPE_CLOSED;
|
||||||
|
|
||||||
|
// write entry to SD
|
||||||
|
return vol_->cacheFlush();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Remove a file.
|
||||||
|
*
|
||||||
|
* The directory entry and all data for the file are deleted.
|
||||||
|
*
|
||||||
|
* \param[in] dirFile The directory that contains the file.
|
||||||
|
* \param[in] path Path for the file to be removed.
|
||||||
|
*
|
||||||
|
* \note This function should not be used to delete the 8.3 version of a
|
||||||
|
* file that has a long name. For example if a file has the long name
|
||||||
|
* "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
* Reasons for failure include the file is a directory, is read only,
|
||||||
|
* \a dirFile is not a directory, \a path is not found
|
||||||
|
* or an I/O error occurred.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::remove(SdBaseFile* dirFile, const char* path) {
|
||||||
|
SdBaseFile file;
|
||||||
|
if (!file.open(dirFile, path, O_WRITE)) goto fail;
|
||||||
|
return file.remove();
|
||||||
|
|
||||||
|
fail:
|
||||||
|
// can't set iostate - static function
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Rename a file or subdirectory.
|
||||||
|
*
|
||||||
|
* \param[in] dirFile Directory for the new path.
|
||||||
|
* \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.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::rename(SdBaseFile* dirFile, const char* newPath) {
|
||||||
|
dir_t entry;
|
||||||
|
uint32_t dirCluster = 0;
|
||||||
|
SdBaseFile file;
|
||||||
|
dir_t* d;
|
||||||
|
|
||||||
|
// must be an open file or subdirectory
|
||||||
|
if (!(isFile() || isSubDir())) goto fail;
|
||||||
|
|
||||||
|
// can't move file
|
||||||
|
if (vol_ != dirFile->vol_) goto fail;
|
||||||
|
|
||||||
|
// sync() and cache directory entry
|
||||||
|
sync();
|
||||||
|
d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||||
|
if (!d) goto fail;
|
||||||
|
|
||||||
|
// save directory entry
|
||||||
|
memcpy(&entry, d, sizeof(entry));
|
||||||
|
|
||||||
|
// mark entry deleted
|
||||||
|
d->name[0] = DIR_NAME_DELETED;
|
||||||
|
|
||||||
|
// make directory entry for new path
|
||||||
|
if (isFile()) {
|
||||||
|
if (!file.open(dirFile, newPath, O_CREAT | O_EXCL | O_WRITE)) {
|
||||||
|
goto restore;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// don't create missing path prefix components
|
||||||
|
if (!file.mkdir(dirFile, newPath, false)) {
|
||||||
|
goto restore;
|
||||||
|
}
|
||||||
|
// save cluster containing new dot dot
|
||||||
|
dirCluster = file.firstCluster_;
|
||||||
|
}
|
||||||
|
// change to new directory entry
|
||||||
|
dirBlock_ = file.dirBlock_;
|
||||||
|
dirIndex_ = file.dirIndex_;
|
||||||
|
|
||||||
|
// mark closed to avoid possible destructor close call
|
||||||
|
file.type_ = FAT_FILE_TYPE_CLOSED;
|
||||||
|
|
||||||
|
// cache new directory entry
|
||||||
|
d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||||
|
if (!d) goto fail;
|
||||||
|
|
||||||
|
// copy all but name field to new directory entry
|
||||||
|
memcpy(&d->attributes, &entry.attributes, sizeof(entry) - sizeof(d->name));
|
||||||
|
|
||||||
|
// update dot dot if directory
|
||||||
|
if (dirCluster) {
|
||||||
|
// get new dot dot
|
||||||
|
uint32_t block = vol_->clusterStartBlock(dirCluster);
|
||||||
|
if (!vol_->cacheRawBlock(block, SdVolume::CACHE_FOR_READ)) goto fail;
|
||||||
|
memcpy(&entry, &vol_->cache()->dir[1], sizeof(entry));
|
||||||
|
|
||||||
|
// free unused cluster
|
||||||
|
if (!vol_->freeChain(dirCluster)) goto fail;
|
||||||
|
|
||||||
|
// store new dot dot
|
||||||
|
block = vol_->clusterStartBlock(firstCluster_);
|
||||||
|
if (!vol_->cacheRawBlock(block, SdVolume::CACHE_FOR_WRITE)) goto fail;
|
||||||
|
memcpy(&vol_->cache()->dir[1], &entry, sizeof(entry));
|
||||||
|
}
|
||||||
|
return vol_->cacheFlush();
|
||||||
|
|
||||||
|
restore:
|
||||||
|
d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||||
|
if (!d) goto fail;
|
||||||
|
// restore entry
|
||||||
|
d->name[0] = entry.name[0];
|
||||||
|
vol_->cacheFlush();
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Remove a directory file.
|
||||||
|
*
|
||||||
|
* The directory file will be removed only if it is empty and is not the
|
||||||
|
* root directory. rmdir() follows DOS and Windows and ignores the
|
||||||
|
* read-only attribute for the directory.
|
||||||
|
*
|
||||||
|
* \note This function should not be used to delete the 8.3 version of a
|
||||||
|
* directory that has a long name. For example if a directory has the
|
||||||
|
* long name "New folder" you should not delete the 8.3 name "NEWFOL~1".
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
* Reasons for failure include the file is not a directory, is the root
|
||||||
|
* directory, is not empty, or an I/O error occurred.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::rmdir() {
|
||||||
|
// must be open subdirectory
|
||||||
|
if (!isSubDir()) goto fail;
|
||||||
|
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
// make sure directory is empty
|
||||||
|
while (curPosition_ < fileSize_) {
|
||||||
|
dir_t* p = readDirCache();
|
||||||
|
if (!p) goto fail;
|
||||||
|
// done if past last used entry
|
||||||
|
if (p->name[0] == DIR_NAME_FREE) break;
|
||||||
|
// skip empty slot, '.' or '..'
|
||||||
|
if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') continue;
|
||||||
|
// error not empty
|
||||||
|
if (DIR_IS_FILE_OR_SUBDIR(p)) goto fail;
|
||||||
|
}
|
||||||
|
// convert empty directory to normal file for remove
|
||||||
|
type_ = FAT_FILE_TYPE_NORMAL;
|
||||||
|
flags_ |= O_WRITE;
|
||||||
|
return remove();
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Recursively delete a directory and all contained files.
|
||||||
|
*
|
||||||
|
* This is like the Unix/Linux 'rm -rf *' if called with the root directory
|
||||||
|
* hence the name.
|
||||||
|
*
|
||||||
|
* Warning - This will remove all contents of the directory including
|
||||||
|
* subdirectories. The directory will then be removed if it is not root.
|
||||||
|
* The read-only attribute for files will be ignored.
|
||||||
|
*
|
||||||
|
* \note This function should not be used to delete the 8.3 version of
|
||||||
|
* a directory that has a long name. See remove() and rmdir().
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::rmRfStar() {
|
||||||
|
uint16_t index;
|
||||||
|
SdBaseFile f;
|
||||||
|
rewind();
|
||||||
|
while (curPosition_ < fileSize_) {
|
||||||
|
// remember position
|
||||||
|
index = curPosition_/32;
|
||||||
|
|
||||||
|
dir_t* p = readDirCache();
|
||||||
|
if (!p) goto fail;
|
||||||
|
|
||||||
|
// done if past last entry
|
||||||
|
if (p->name[0] == DIR_NAME_FREE) break;
|
||||||
|
|
||||||
|
// skip empty slot or '.' or '..'
|
||||||
|
if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') continue;
|
||||||
|
|
||||||
|
// skip if part of long file name or volume label in root
|
||||||
|
if (!DIR_IS_FILE_OR_SUBDIR(p)) continue;
|
||||||
|
|
||||||
|
if (!f.open(this, index, O_READ)) goto fail;
|
||||||
|
if (f.isSubDir()) {
|
||||||
|
// recursively delete
|
||||||
|
if (!f.rmRfStar()) goto fail;
|
||||||
|
} else {
|
||||||
|
// ignore read-only
|
||||||
|
f.flags_ |= O_WRITE;
|
||||||
|
if (!f.remove()) goto fail;
|
||||||
|
}
|
||||||
|
// position to next entry if required
|
||||||
|
if (curPosition_ != (32*(index + 1))) {
|
||||||
|
if (!seekSet(32*(index + 1))) goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// don't try to delete root
|
||||||
|
if (!isRoot()) {
|
||||||
|
if (!rmdir()) goto fail;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** 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] oflag Values for \a oflag are constructed by a bitwise-inclusive
|
||||||
|
* OR of open flags. see SdBaseFile::open(SdBaseFile*, const char*, uint8_t).
|
||||||
|
*/
|
||||||
|
SdBaseFile::SdBaseFile(const char* path, uint8_t oflag) {
|
||||||
|
type_ = FAT_FILE_TYPE_CLOSED;
|
||||||
|
writeError = false;
|
||||||
|
open(path, oflag);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Sets a file's position.
|
||||||
|
*
|
||||||
|
* \param[in] pos The new position in bytes from the beginning of the file.
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::seekSet(uint32_t pos) {
|
||||||
|
uint32_t nCur;
|
||||||
|
uint32_t nNew;
|
||||||
|
// error if file not open or seek past end of file
|
||||||
|
if (!isOpen() || pos > fileSize_) goto fail;
|
||||||
|
|
||||||
|
if (type_ == FAT_FILE_TYPE_ROOT_FIXED) {
|
||||||
|
curPosition_ = pos;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (pos == 0) {
|
||||||
|
// set position to start of file
|
||||||
|
curCluster_ = 0;
|
||||||
|
curPosition_ = 0;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
// calculate cluster index for cur and new position
|
||||||
|
nCur = (curPosition_ - 1) >> (vol_->clusterSizeShift_ + 9);
|
||||||
|
nNew = (pos - 1) >> (vol_->clusterSizeShift_ + 9);
|
||||||
|
|
||||||
|
if (nNew < nCur || curPosition_ == 0) {
|
||||||
|
// must follow chain from first cluster
|
||||||
|
curCluster_ = firstCluster_;
|
||||||
|
} else {
|
||||||
|
// advance from curPosition
|
||||||
|
nNew -= nCur;
|
||||||
|
}
|
||||||
|
while (nNew--) {
|
||||||
|
if (!vol_->fatGet(curCluster_, &curCluster_)) goto fail;
|
||||||
|
}
|
||||||
|
curPosition_ = pos;
|
||||||
|
|
||||||
|
done:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
void SdBaseFile::setpos(fpos_t* pos) {
|
||||||
|
curPosition_ = pos->position;
|
||||||
|
curCluster_ = pos->cluster;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** The sync() call causes all modified data and directory fields
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::sync() {
|
||||||
|
// only allow open files and directories
|
||||||
|
if (!isOpen()) goto fail;
|
||||||
|
|
||||||
|
if (flags_ & F_FILE_DIR_DIRTY) {
|
||||||
|
dir_t* d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||||
|
// check for deleted by another open file object
|
||||||
|
if (!d || d->name[0] == DIR_NAME_DELETED) goto fail;
|
||||||
|
|
||||||
|
// do not set filesize for dir files
|
||||||
|
if (!isDir()) d->fileSize = fileSize_;
|
||||||
|
|
||||||
|
// update first cluster fields
|
||||||
|
d->firstClusterLow = firstCluster_ & 0XFFFF;
|
||||||
|
d->firstClusterHigh = firstCluster_ >> 16;
|
||||||
|
|
||||||
|
// set modify time if user supplied a callback date/time function
|
||||||
|
if (dateTime_) {
|
||||||
|
dateTime_(&d->lastWriteDate, &d->lastWriteTime);
|
||||||
|
d->lastAccessDate = d->lastWriteDate;
|
||||||
|
}
|
||||||
|
// clear directory dirty
|
||||||
|
flags_ &= ~F_FILE_DIR_DIRTY;
|
||||||
|
}
|
||||||
|
return vol_->cacheFlush();
|
||||||
|
|
||||||
|
fail:
|
||||||
|
writeError = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Copy a file's timestamps
|
||||||
|
*
|
||||||
|
* \param[in] file File to copy timestamps from.
|
||||||
|
*
|
||||||
|
* \note
|
||||||
|
* Modify and access timestamps may be overwritten if a date time callback
|
||||||
|
* function has been set by dateTimeCallback().
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::timestamp(SdBaseFile* file) {
|
||||||
|
dir_t* d;
|
||||||
|
dir_t dir;
|
||||||
|
|
||||||
|
// get timestamps
|
||||||
|
if (!file->dirEntry(&dir)) goto fail;
|
||||||
|
|
||||||
|
// update directory fields
|
||||||
|
if (!sync()) goto fail;
|
||||||
|
|
||||||
|
d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||||
|
if (!d) goto fail;
|
||||||
|
|
||||||
|
// copy timestamps
|
||||||
|
d->lastAccessDate = dir.lastAccessDate;
|
||||||
|
d->creationDate = dir.creationDate;
|
||||||
|
d->creationTime = dir.creationTime;
|
||||||
|
d->creationTimeTenths = dir.creationTimeTenths;
|
||||||
|
d->lastWriteDate = dir.lastWriteDate;
|
||||||
|
d->lastWriteTime = dir.lastWriteTime;
|
||||||
|
|
||||||
|
// write back entry
|
||||||
|
return vol_->cacheFlush();
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Set a file's timestamps in its directory entry.
|
||||||
|
*
|
||||||
|
* \param[in] flags Values for \a flags are constructed by a bitwise-inclusive
|
||||||
|
* OR of flags from the following list
|
||||||
|
*
|
||||||
|
* T_ACCESS - Set the file's last access date.
|
||||||
|
*
|
||||||
|
* T_CREATE - Set the file's creation date and time.
|
||||||
|
*
|
||||||
|
* T_WRITE - Set the file's last write/modification date and time.
|
||||||
|
*
|
||||||
|
* \param[in] year Valid range 1980 - 2107 inclusive.
|
||||||
|
*
|
||||||
|
* \param[in] month Valid range 1 - 12 inclusive.
|
||||||
|
*
|
||||||
|
* \param[in] day Valid range 1 - 31 inclusive.
|
||||||
|
*
|
||||||
|
* \param[in] hour Valid range 0 - 23 inclusive.
|
||||||
|
*
|
||||||
|
* \param[in] minute Valid range 0 - 59 inclusive.
|
||||||
|
*
|
||||||
|
* \param[in] second Valid range 0 - 59 inclusive
|
||||||
|
*
|
||||||
|
* \note It is possible to set an invalid date since there is no check for
|
||||||
|
* the number of days in a month.
|
||||||
|
*
|
||||||
|
* \note
|
||||||
|
* Modify and access timestamps may be overwritten if a date time callback
|
||||||
|
* function has been set by dateTimeCallback().
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::timestamp(uint8_t flags, uint16_t year, uint8_t month,
|
||||||
|
uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
|
||||||
|
uint16_t dirDate;
|
||||||
|
uint16_t dirTime;
|
||||||
|
dir_t* d;
|
||||||
|
|
||||||
|
if (!isOpen()
|
||||||
|
|| year < 1980
|
||||||
|
|| year > 2107
|
||||||
|
|| month < 1
|
||||||
|
|| month > 12
|
||||||
|
|| day < 1
|
||||||
|
|| day > 31
|
||||||
|
|| hour > 23
|
||||||
|
|| minute > 59
|
||||||
|
|| second > 59) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
// update directory entry
|
||||||
|
if (!sync()) goto fail;
|
||||||
|
|
||||||
|
d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
||||||
|
if (!d) goto fail;
|
||||||
|
|
||||||
|
dirDate = FAT_DATE(year, month, day);
|
||||||
|
dirTime = FAT_TIME(hour, minute, second);
|
||||||
|
if (flags & T_ACCESS) {
|
||||||
|
d->lastAccessDate = dirDate;
|
||||||
|
}
|
||||||
|
if (flags & T_CREATE) {
|
||||||
|
d->creationDate = dirDate;
|
||||||
|
d->creationTime = dirTime;
|
||||||
|
// seems to be units of 1/100 second not 1/10 as Microsoft states
|
||||||
|
d->creationTimeTenths = second & 1 ? 100 : 0;
|
||||||
|
}
|
||||||
|
if (flags & T_WRITE) {
|
||||||
|
d->lastWriteDate = dirDate;
|
||||||
|
d->lastWriteTime = dirTime;
|
||||||
|
}
|
||||||
|
return vol_->cacheFlush();
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Truncate a file to a specified length. The current file position
|
||||||
|
* will be maintained if it is less than or equal to \a length otherwise
|
||||||
|
* it will be set to end of file.
|
||||||
|
*
|
||||||
|
* \param[in] length The desired length for the file.
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
* Reasons for failure include file is read only, file is a directory,
|
||||||
|
* \a length is greater than the current file size or an I/O error occurs.
|
||||||
|
*/
|
||||||
|
bool SdBaseFile::truncate(uint32_t length) {
|
||||||
|
uint32_t newPos;
|
||||||
|
// error if not a normal file or read-only
|
||||||
|
if (!isFile() || !(flags_ & O_WRITE)) goto fail;
|
||||||
|
|
||||||
|
// error if length is greater than current size
|
||||||
|
if (length > fileSize_) goto fail;
|
||||||
|
|
||||||
|
// fileSize and length are zero - nothing to do
|
||||||
|
if (fileSize_ == 0) return true;
|
||||||
|
|
||||||
|
// remember position for seek after truncation
|
||||||
|
newPos = curPosition_ > length ? length : curPosition_;
|
||||||
|
|
||||||
|
// position to last cluster in truncated file
|
||||||
|
if (!seekSet(length)) goto fail;
|
||||||
|
|
||||||
|
if (length == 0) {
|
||||||
|
// free all clusters
|
||||||
|
if (!vol_->freeChain(firstCluster_)) goto fail;
|
||||||
|
firstCluster_ = 0;
|
||||||
|
} else {
|
||||||
|
uint32_t toFree;
|
||||||
|
if (!vol_->fatGet(curCluster_, &toFree)) goto fail;
|
||||||
|
|
||||||
|
if (!vol_->isEOC(toFree)) {
|
||||||
|
// free extra clusters
|
||||||
|
if (!vol_->freeChain(toFree)) goto fail;
|
||||||
|
|
||||||
|
// current cluster is end of chain
|
||||||
|
if (!vol_->fatPutEOC(curCluster_)) goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileSize_ = length;
|
||||||
|
|
||||||
|
// need to update directory entry
|
||||||
|
flags_ |= F_FILE_DIR_DIRTY;
|
||||||
|
|
||||||
|
if (!sync()) goto fail;
|
||||||
|
|
||||||
|
// set file to correct position
|
||||||
|
return seekSet(newPos);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Write data to an open file.
|
||||||
|
*
|
||||||
|
* \note Data is moved to the cache but may not be written to the
|
||||||
|
* storage device until sync() is called.
|
||||||
|
*
|
||||||
|
* \param[in] buf Pointer to the location of the data to be written.
|
||||||
|
*
|
||||||
|
* \param[in] nbyte Number of bytes to write.
|
||||||
|
*
|
||||||
|
* \return For success write() returns the number of bytes written, always
|
||||||
|
* \a nbyte. If an error occurs, write() returns -1. Possible errors
|
||||||
|
* include write() is called before a file has been opened, write is called
|
||||||
|
* for a read-only file, device is full, a corrupt file system or an I/O error.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int16_t SdBaseFile::write(const void* buf, uint16_t nbyte) {
|
||||||
|
// convert void* to uint8_t* - must be before goto statements
|
||||||
|
const uint8_t* src = reinterpret_cast<const uint8_t*>(buf);
|
||||||
|
|
||||||
|
// number of bytes left to write - must be before goto statements
|
||||||
|
uint16_t nToWrite = nbyte;
|
||||||
|
|
||||||
|
// error if not a normal file or is read-only
|
||||||
|
if (!isFile() || !(flags_ & O_WRITE)) goto fail;
|
||||||
|
|
||||||
|
// seek to end of file if append flag
|
||||||
|
if ((flags_ & O_APPEND) && curPosition_ != fileSize_) {
|
||||||
|
if (!seekEnd()) goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (nToWrite > 0) {
|
||||||
|
uint8_t blockOfCluster = vol_->blockOfCluster(curPosition_);
|
||||||
|
uint16_t blockOffset = curPosition_ & 0X1FF;
|
||||||
|
if (blockOfCluster == 0 && blockOffset == 0) {
|
||||||
|
// start of new cluster
|
||||||
|
if (curCluster_ == 0) {
|
||||||
|
if (firstCluster_ == 0) {
|
||||||
|
// allocate first cluster of file
|
||||||
|
if (!addCluster()) goto fail;
|
||||||
|
} else {
|
||||||
|
curCluster_ = firstCluster_;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uint32_t next;
|
||||||
|
if (!vol_->fatGet(curCluster_, &next)) goto fail;
|
||||||
|
if (vol_->isEOC(next)) {
|
||||||
|
// add cluster if at end of chain
|
||||||
|
if (!addCluster()) goto fail;
|
||||||
|
} else {
|
||||||
|
curCluster_ = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// max space in block
|
||||||
|
uint16_t n = 512 - blockOffset;
|
||||||
|
|
||||||
|
// lesser of space and amount to write
|
||||||
|
if (n > nToWrite) n = nToWrite;
|
||||||
|
|
||||||
|
// block for data write
|
||||||
|
uint32_t block = vol_->clusterStartBlock(curCluster_) + blockOfCluster;
|
||||||
|
if (n == 512) {
|
||||||
|
// full block - don't need to use cache
|
||||||
|
if (vol_->cacheBlockNumber() == block) {
|
||||||
|
// invalidate cache if block is in cache
|
||||||
|
vol_->cacheSetBlockNumber(0XFFFFFFFF, false);
|
||||||
|
}
|
||||||
|
if (!vol_->writeBlock(block, src)) goto fail;
|
||||||
|
} else {
|
||||||
|
if (blockOffset == 0 && curPosition_ >= fileSize_) {
|
||||||
|
// start of new block don't need to read into cache
|
||||||
|
if (!vol_->cacheFlush()) goto fail;
|
||||||
|
// set cache dirty and SD address of block
|
||||||
|
vol_->cacheSetBlockNumber(block, true);
|
||||||
|
} else {
|
||||||
|
// rewrite part of block
|
||||||
|
if (!vol_->cacheRawBlock(block, SdVolume::CACHE_FOR_WRITE)) goto fail;
|
||||||
|
}
|
||||||
|
uint8_t* dst = vol_->cache()->data + blockOffset;
|
||||||
|
memcpy(dst, src, n);
|
||||||
|
}
|
||||||
|
curPosition_ += n;
|
||||||
|
src += n;
|
||||||
|
nToWrite -= n;
|
||||||
|
}
|
||||||
|
if (curPosition_ > fileSize_) {
|
||||||
|
// update fileSize and insure sync will update dir entry
|
||||||
|
fileSize_ = curPosition_;
|
||||||
|
flags_ |= F_FILE_DIR_DIRTY;
|
||||||
|
} else if (dateTime_ && nbyte) {
|
||||||
|
// insure sync will update modified date and time
|
||||||
|
flags_ |= F_FILE_DIR_DIRTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags_ & O_SYNC) {
|
||||||
|
if (!sync()) goto fail;
|
||||||
|
}
|
||||||
|
return nbyte;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
// return for write error
|
||||||
|
writeError = true;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// suppress cpplint warnings with NOLINT comment
|
||||||
|
#if ALLOW_DEPRECATED_FUNCTIONS && !defined(DOXYGEN)
|
||||||
|
void (*SdBaseFile::oldDateTime_)(uint16_t& date, uint16_t& time) = 0; // NOLINT
|
||||||
|
#endif // ALLOW_DEPRECATED_FUNCTIONS
|
@ -0,0 +1,489 @@
|
|||||||
|
/* Arduino SdFat Library
|
||||||
|
* Copyright (C) 2009 by William Greiman
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This Library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with the Arduino SdFat Library. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifndef SdBaseFile_h
|
||||||
|
#define SdBaseFile_h
|
||||||
|
/**
|
||||||
|
* \file
|
||||||
|
* \brief SdBaseFile class
|
||||||
|
*/
|
||||||
|
#include <avr/pgmspace.h>
|
||||||
|
#if ARDUINO < 100
|
||||||
|
#include <WProgram.h>
|
||||||
|
#else // ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#endif // ARDUINO
|
||||||
|
#include "SdFatConfig.h"
|
||||||
|
#include "SdVolume.h"
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* \struct fpos_t
|
||||||
|
* \brief internal type for istream
|
||||||
|
* do not use in user apps
|
||||||
|
*/
|
||||||
|
struct fpos_t {
|
||||||
|
/** stream position */
|
||||||
|
uint32_t position;
|
||||||
|
/** cluster for position */
|
||||||
|
uint32_t cluster;
|
||||||
|
fpos_t() : position(0), cluster(0) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// use the gnu style oflag in open()
|
||||||
|
/** open() oflag for reading */
|
||||||
|
uint8_t const O_READ = 0X01;
|
||||||
|
/** open() oflag - same as O_IN */
|
||||||
|
uint8_t const O_RDONLY = O_READ;
|
||||||
|
/** open() oflag for write */
|
||||||
|
uint8_t const O_WRITE = 0X02;
|
||||||
|
/** open() oflag - same as O_WRITE */
|
||||||
|
uint8_t const O_WRONLY = O_WRITE;
|
||||||
|
/** open() oflag for reading and writing */
|
||||||
|
uint8_t const O_RDWR = (O_READ | O_WRITE);
|
||||||
|
/** open() oflag mask for access modes */
|
||||||
|
uint8_t const O_ACCMODE = (O_READ | O_WRITE);
|
||||||
|
/** The file offset shall be set to the end of the file prior to each write. */
|
||||||
|
uint8_t const O_APPEND = 0X04;
|
||||||
|
/** synchronous writes - call sync() after each write */
|
||||||
|
uint8_t const O_SYNC = 0X08;
|
||||||
|
/** truncate the file to zero length */
|
||||||
|
uint8_t const O_TRUNC = 0X10;
|
||||||
|
/** set the initial position at the end of the file */
|
||||||
|
uint8_t const O_AT_END = 0X20;
|
||||||
|
/** create the file if nonexistent */
|
||||||
|
uint8_t const O_CREAT = 0X40;
|
||||||
|
/** If O_CREAT and O_EXCL are set, open() shall fail if the file exists */
|
||||||
|
uint8_t const O_EXCL = 0X80;
|
||||||
|
|
||||||
|
// SdBaseFile class static and const definitions
|
||||||
|
// 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 recursive list of subdirectories */
|
||||||
|
uint8_t const LS_R = 4;
|
||||||
|
|
||||||
|
|
||||||
|
// flags for timestamp
|
||||||
|
/** set the file's last access date */
|
||||||
|
uint8_t const T_ACCESS = 1;
|
||||||
|
/** set the file's creation date and time */
|
||||||
|
uint8_t const T_CREATE = 2;
|
||||||
|
/** Set the file's write date and time */
|
||||||
|
uint8_t const T_WRITE = 4;
|
||||||
|
// values for type_
|
||||||
|
/** This file has not been opened. */
|
||||||
|
uint8_t const FAT_FILE_TYPE_CLOSED = 0;
|
||||||
|
/** A normal file */
|
||||||
|
uint8_t const FAT_FILE_TYPE_NORMAL = 1;
|
||||||
|
/** A FAT12 or FAT16 root directory */
|
||||||
|
uint8_t const FAT_FILE_TYPE_ROOT_FIXED = 2;
|
||||||
|
/** A FAT32 root directory */
|
||||||
|
uint8_t const FAT_FILE_TYPE_ROOT32 = 3;
|
||||||
|
/** A subdirectory file*/
|
||||||
|
uint8_t const FAT_FILE_TYPE_SUBDIR = 4;
|
||||||
|
/** Test value for directory type */
|
||||||
|
uint8_t const FAT_FILE_TYPE_MIN_DIR = FAT_FILE_TYPE_ROOT_FIXED;
|
||||||
|
|
||||||
|
/** date field for FAT directory entry
|
||||||
|
* \param[in] year [1980,2107]
|
||||||
|
* \param[in] month [1,12]
|
||||||
|
* \param[in] day [1,31]
|
||||||
|
*
|
||||||
|
* \return Packed date for dir_t entry.
|
||||||
|
*/
|
||||||
|
static inline uint16_t FAT_DATE(uint16_t year, uint8_t month, uint8_t day) {
|
||||||
|
return (year - 1980) << 9 | month << 5 | day;
|
||||||
|
}
|
||||||
|
/** year part of FAT directory date field
|
||||||
|
* \param[in] fatDate Date in packed dir format.
|
||||||
|
*
|
||||||
|
* \return Extracted year [1980,2107]
|
||||||
|
*/
|
||||||
|
static inline uint16_t FAT_YEAR(uint16_t fatDate) {
|
||||||
|
return 1980 + (fatDate >> 9);
|
||||||
|
}
|
||||||
|
/** month part of FAT directory date field
|
||||||
|
* \param[in] fatDate Date in packed dir format.
|
||||||
|
*
|
||||||
|
* \return Extracted month [1,12]
|
||||||
|
*/
|
||||||
|
static inline uint8_t FAT_MONTH(uint16_t fatDate) {
|
||||||
|
return (fatDate >> 5) & 0XF;
|
||||||
|
}
|
||||||
|
/** day part of FAT directory date field
|
||||||
|
* \param[in] fatDate Date in packed dir format.
|
||||||
|
*
|
||||||
|
* \return Extracted day [1,31]
|
||||||
|
*/
|
||||||
|
static inline uint8_t FAT_DAY(uint16_t fatDate) {
|
||||||
|
return fatDate & 0X1F;
|
||||||
|
}
|
||||||
|
/** time field for FAT directory entry
|
||||||
|
* \param[in] hour [0,23]
|
||||||
|
* \param[in] minute [0,59]
|
||||||
|
* \param[in] second [0,59]
|
||||||
|
*
|
||||||
|
* \return Packed time for dir_t entry.
|
||||||
|
*/
|
||||||
|
static inline uint16_t FAT_TIME(uint8_t hour, uint8_t minute, uint8_t second) {
|
||||||
|
return hour << 11 | minute << 5 | second >> 1;
|
||||||
|
}
|
||||||
|
/** hour part of FAT directory time field
|
||||||
|
* \param[in] fatTime Time in packed dir format.
|
||||||
|
*
|
||||||
|
* \return Extracted hour [0,23]
|
||||||
|
*/
|
||||||
|
static inline uint8_t FAT_HOUR(uint16_t fatTime) {
|
||||||
|
return fatTime >> 11;
|
||||||
|
}
|
||||||
|
/** minute part of FAT directory time field
|
||||||
|
* \param[in] fatTime Time in packed dir format.
|
||||||
|
*
|
||||||
|
* \return Extracted minute [0,59]
|
||||||
|
*/
|
||||||
|
static inline uint8_t FAT_MINUTE(uint16_t fatTime) {
|
||||||
|
return(fatTime >> 5) & 0X3F;
|
||||||
|
}
|
||||||
|
/** second part of FAT directory time field
|
||||||
|
* Note second/2 is stored in packed time.
|
||||||
|
*
|
||||||
|
* \param[in] fatTime Time in packed dir format.
|
||||||
|
*
|
||||||
|
* \return Extracted second [0,58]
|
||||||
|
*/
|
||||||
|
static inline uint8_t FAT_SECOND(uint16_t fatTime) {
|
||||||
|
return 2*(fatTime & 0X1F);
|
||||||
|
}
|
||||||
|
/** Default date for file timestamps is 1 Jan 2000 */
|
||||||
|
uint16_t const FAT_DEFAULT_DATE = ((2000 - 1980) << 9) | (1 << 5) | 1;
|
||||||
|
/** Default time for file timestamp is 1 am */
|
||||||
|
uint16_t const FAT_DEFAULT_TIME = (1 << 11);
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* \class SdBaseFile
|
||||||
|
* \brief Base class for SdFile with Print and C++ streams.
|
||||||
|
*/
|
||||||
|
class SdBaseFile {
|
||||||
|
public:
|
||||||
|
/** Create an instance. */
|
||||||
|
SdBaseFile() : writeError(false), type_(FAT_FILE_TYPE_CLOSED) {}
|
||||||
|
SdBaseFile(const char* path, uint8_t oflag);
|
||||||
|
~SdBaseFile() {if(isOpen()) close();}
|
||||||
|
/**
|
||||||
|
* writeError is set to true if an error occurs during a write().
|
||||||
|
* Set writeError to false before calling print() and/or write() and check
|
||||||
|
* for true after calls to print() and/or write().
|
||||||
|
*/
|
||||||
|
bool writeError;
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
// helpers for stream classes
|
||||||
|
/** get position for streams
|
||||||
|
* \param[out] pos struct to receive position
|
||||||
|
*/
|
||||||
|
void getpos(fpos_t* pos);
|
||||||
|
/** set position for streams
|
||||||
|
* \param[out] pos struct with value for new position
|
||||||
|
*/
|
||||||
|
void setpos(fpos_t* pos);
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
bool close();
|
||||||
|
bool contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock);
|
||||||
|
bool createContiguous(SdBaseFile* dirFile,
|
||||||
|
const char* path, uint32_t size);
|
||||||
|
/** \return The current cluster number for a file or directory. */
|
||||||
|
uint32_t curCluster() const {return curCluster_;}
|
||||||
|
/** \return The current position for a file or directory. */
|
||||||
|
uint32_t curPosition() const {return curPosition_;}
|
||||||
|
/** \return Current working directory */
|
||||||
|
static SdBaseFile* cwd() {return cwd_;}
|
||||||
|
/** Set the date/time callback function
|
||||||
|
*
|
||||||
|
* \param[in] dateTime The user's call back function. The callback
|
||||||
|
* function is of the form:
|
||||||
|
*
|
||||||
|
* \code
|
||||||
|
* void dateTime(uint16_t* date, uint16_t* time) {
|
||||||
|
* uint16_t year;
|
||||||
|
* uint8_t month, day, hour, minute, second;
|
||||||
|
*
|
||||||
|
* // User gets date and time from GPS or real-time clock here
|
||||||
|
*
|
||||||
|
* // return date using FAT_DATE macro to format fields
|
||||||
|
* *date = FAT_DATE(year, month, day);
|
||||||
|
*
|
||||||
|
* // return time using FAT_TIME macro to format fields
|
||||||
|
* *time = FAT_TIME(hour, minute, second);
|
||||||
|
* }
|
||||||
|
* \endcode
|
||||||
|
*
|
||||||
|
* Sets the function that is called when a file is created or when
|
||||||
|
* a file's directory entry is modified by sync(). All timestamps,
|
||||||
|
* access, creation, and modify, are set when a file is created.
|
||||||
|
* sync() maintains the last access date and last modify date/time.
|
||||||
|
*
|
||||||
|
* See the timestamp() function.
|
||||||
|
*/
|
||||||
|
static void dateTimeCallback(
|
||||||
|
void (*dateTime)(uint16_t* date, uint16_t* time)) {
|
||||||
|
dateTime_ = dateTime;
|
||||||
|
}
|
||||||
|
/** Cancel the date/time callback function. */
|
||||||
|
static void dateTimeCallbackCancel() {dateTime_ = 0;}
|
||||||
|
bool dirEntry(dir_t* dir);
|
||||||
|
static void dirName(const dir_t& dir, char* name);
|
||||||
|
bool exists(const char* name);
|
||||||
|
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 fileSize_;}
|
||||||
|
/** \return The first cluster number for a file or directory. */
|
||||||
|
uint32_t firstCluster() const {return firstCluster_;}
|
||||||
|
bool getFilename(char* name);
|
||||||
|
/** \return True if this is a directory else false. */
|
||||||
|
bool isDir() const {return type_ >= FAT_FILE_TYPE_MIN_DIR;}
|
||||||
|
/** \return True if this is a normal file else false. */
|
||||||
|
bool isFile() const {return type_ == FAT_FILE_TYPE_NORMAL;}
|
||||||
|
/** \return True if this is an open file/directory else false. */
|
||||||
|
bool isOpen() const {return type_ != FAT_FILE_TYPE_CLOSED;}
|
||||||
|
/** \return True if this is a subdirectory else false. */
|
||||||
|
bool isSubDir() const {return type_ == FAT_FILE_TYPE_SUBDIR;}
|
||||||
|
/** \return True if this is the root directory. */
|
||||||
|
bool isRoot() const {
|
||||||
|
return type_ == FAT_FILE_TYPE_ROOT_FIXED || type_ == FAT_FILE_TYPE_ROOT32;
|
||||||
|
}
|
||||||
|
void ls(Print* pr, uint8_t flags = 0, uint8_t indent = 0);
|
||||||
|
void ls(uint8_t flags = 0);
|
||||||
|
bool mkdir(SdBaseFile* dir, const char* path, bool pFlag = true);
|
||||||
|
// alias for backward compactability
|
||||||
|
bool makeDir(SdBaseFile* dir, const char* path) {
|
||||||
|
return mkdir(dir, path, false);
|
||||||
|
}
|
||||||
|
bool open(SdBaseFile* dirFile, uint16_t index, uint8_t oflag);
|
||||||
|
bool open(SdBaseFile* dirFile, const char* path, uint8_t oflag);
|
||||||
|
bool open(const char* path, uint8_t oflag = O_READ);
|
||||||
|
bool openNext(SdBaseFile* dirFile, uint8_t oflag);
|
||||||
|
bool openRoot(SdVolume* vol);
|
||||||
|
int peek();
|
||||||
|
static void printFatDate(uint16_t fatDate);
|
||||||
|
static void printFatDate(Print* pr, uint16_t fatDate);
|
||||||
|
static void printFatTime(uint16_t fatTime);
|
||||||
|
static void printFatTime(Print* pr, uint16_t fatTime);
|
||||||
|
bool printName();
|
||||||
|
int16_t read();
|
||||||
|
int16_t read(void* buf, uint16_t nbyte);
|
||||||
|
int8_t readDir(dir_t* dir);
|
||||||
|
static bool remove(SdBaseFile* dirFile, const char* path);
|
||||||
|
bool remove();
|
||||||
|
/** Set the file's current position to zero. */
|
||||||
|
void rewind() {seekSet(0);}
|
||||||
|
bool rename(SdBaseFile* dirFile, const char* newPath);
|
||||||
|
bool rmdir();
|
||||||
|
// for backward compatibility
|
||||||
|
bool rmDir() {return rmdir();}
|
||||||
|
bool rmRfStar();
|
||||||
|
/** Set the files position to current position + \a pos. See seekSet().
|
||||||
|
* \param[in] offset The new position in bytes from the current position.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool seekCur(int32_t offset) {
|
||||||
|
return seekSet(curPosition_ + offset);
|
||||||
|
}
|
||||||
|
/** Set the files position to end-of-file + \a offset. See seekSet().
|
||||||
|
* \param[in] offset The new position in bytes from end-of-file.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool seekEnd(int32_t offset = 0) {return seekSet(fileSize_ + offset);}
|
||||||
|
bool seekSet(uint32_t pos);
|
||||||
|
bool sync();
|
||||||
|
bool timestamp(SdBaseFile* file);
|
||||||
|
bool timestamp(uint8_t flag, uint16_t year, uint8_t month, uint8_t day,
|
||||||
|
uint8_t hour, uint8_t minute, uint8_t second);
|
||||||
|
/** Type of file. You should use isFile() or isDir() instead of type()
|
||||||
|
* if possible.
|
||||||
|
*
|
||||||
|
* \return The file or directory type.
|
||||||
|
*/
|
||||||
|
uint8_t type() const {return type_;}
|
||||||
|
bool truncate(uint32_t size);
|
||||||
|
/** \return SdVolume that contains this file. */
|
||||||
|
SdVolume* volume() const {return vol_;}
|
||||||
|
int16_t write(const void* buf, uint16_t nbyte);
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
private:
|
||||||
|
// allow SdFat to set cwd_
|
||||||
|
friend class SdFat;
|
||||||
|
// global pointer to cwd dir
|
||||||
|
static SdBaseFile* cwd_;
|
||||||
|
// data time callback function
|
||||||
|
static void (*dateTime_)(uint16_t* date, uint16_t* time);
|
||||||
|
// bits defined in flags_
|
||||||
|
// should be 0X0F
|
||||||
|
static uint8_t const F_OFLAG = (O_ACCMODE | O_APPEND | O_SYNC);
|
||||||
|
// sync of directory entry required
|
||||||
|
static uint8_t const F_FILE_DIR_DIRTY = 0X80;
|
||||||
|
|
||||||
|
// private data
|
||||||
|
uint8_t flags_; // See above for definition of flags_ bits
|
||||||
|
uint8_t fstate_; // error and eof indicator
|
||||||
|
uint8_t type_; // type of file see above for values
|
||||||
|
uint32_t curCluster_; // cluster for current file position
|
||||||
|
uint32_t curPosition_; // current file position in bytes from beginning
|
||||||
|
uint32_t dirBlock_; // block for this files directory entry
|
||||||
|
uint8_t dirIndex_; // index of directory entry in dirBlock
|
||||||
|
uint32_t fileSize_; // file size in bytes
|
||||||
|
uint32_t firstCluster_; // first cluster of file
|
||||||
|
SdVolume* vol_; // volume where file is located
|
||||||
|
|
||||||
|
/** experimental don't use */
|
||||||
|
bool openParent(SdBaseFile* dir);
|
||||||
|
// private functions
|
||||||
|
bool addCluster();
|
||||||
|
bool addDirCluster();
|
||||||
|
dir_t* cacheDirEntry(uint8_t action);
|
||||||
|
int8_t lsPrintNext(Print *pr, uint8_t flags, uint8_t indent);
|
||||||
|
static bool make83Name(const char* str, uint8_t* name, const char** ptr);
|
||||||
|
bool mkdir(SdBaseFile* parent, const uint8_t dname[11]);
|
||||||
|
bool open(SdBaseFile* dirFile, const uint8_t dname[11], uint8_t oflag);
|
||||||
|
bool openCachedEntry(uint8_t cacheIndex, uint8_t oflags);
|
||||||
|
dir_t* readDirCache();
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// to be deleted
|
||||||
|
static void printDirName(const dir_t& dir,
|
||||||
|
uint8_t width, bool printSlash);
|
||||||
|
static void printDirName(Print* pr, const dir_t& dir,
|
||||||
|
uint8_t width, bool printSlash);
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Deprecated functions - suppress cpplint warnings with NOLINT comment
|
||||||
|
#if ALLOW_DEPRECATED_FUNCTIONS && !defined(DOXYGEN)
|
||||||
|
public:
|
||||||
|
/** \deprecated Use:
|
||||||
|
* bool contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock);
|
||||||
|
* \param[out] bgnBlock the first block address for the file.
|
||||||
|
* \param[out] endBlock the last block address for the file.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool contiguousRange(uint32_t& bgnBlock, uint32_t& endBlock) { // NOLINT
|
||||||
|
return contiguousRange(&bgnBlock, &endBlock);
|
||||||
|
}
|
||||||
|
/** \deprecated Use:
|
||||||
|
* bool createContiguous(SdBaseFile* dirFile,
|
||||||
|
* const char* path, uint32_t size)
|
||||||
|
* \param[in] dirFile The directory where the file will be created.
|
||||||
|
* \param[in] path A path with a valid DOS 8.3 file name.
|
||||||
|
* \param[in] size The desired file size.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool createContiguous(SdBaseFile& dirFile, // NOLINT
|
||||||
|
const char* path, uint32_t size) {
|
||||||
|
return createContiguous(&dirFile, path, size);
|
||||||
|
}
|
||||||
|
/** \deprecated Use:
|
||||||
|
* static void dateTimeCallback(
|
||||||
|
* void (*dateTime)(uint16_t* date, uint16_t* time));
|
||||||
|
* \param[in] dateTime The user's call back function.
|
||||||
|
*/
|
||||||
|
static void dateTimeCallback(
|
||||||
|
void (*dateTime)(uint16_t& date, uint16_t& time)) { // NOLINT
|
||||||
|
oldDateTime_ = dateTime;
|
||||||
|
dateTime_ = dateTime ? oldToNew : 0;
|
||||||
|
}
|
||||||
|
/** \deprecated Use: bool dirEntry(dir_t* dir);
|
||||||
|
* \param[out] dir Location for return of the file's directory entry.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool dirEntry(dir_t& dir) {return dirEntry(&dir);} // NOLINT
|
||||||
|
/** \deprecated Use:
|
||||||
|
* bool mkdir(SdBaseFile* dir, const char* path);
|
||||||
|
* \param[in] dir An open SdFat instance for the directory that will contain
|
||||||
|
* the new directory.
|
||||||
|
* \param[in] path A path with a valid 8.3 DOS name for the new directory.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool mkdir(SdBaseFile& dir, const char* path) { // NOLINT
|
||||||
|
return mkdir(&dir, path);
|
||||||
|
}
|
||||||
|
/** \deprecated Use:
|
||||||
|
* bool open(SdBaseFile* dirFile, const char* path, uint8_t oflag);
|
||||||
|
* \param[in] dirFile An open SdFat instance for the directory containing the
|
||||||
|
* file to be opened.
|
||||||
|
* \param[in] path A path with a valid 8.3 DOS name for the file.
|
||||||
|
* \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
|
||||||
|
* OR of flags O_READ, O_WRITE, O_TRUNC, and O_SYNC.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool open(SdBaseFile& dirFile, // NOLINT
|
||||||
|
const char* path, uint8_t oflag) {
|
||||||
|
return open(&dirFile, path, oflag);
|
||||||
|
}
|
||||||
|
/** \deprecated Do not use in new apps
|
||||||
|
* \param[in] dirFile An open SdFat instance for the directory containing the
|
||||||
|
* file to be opened.
|
||||||
|
* \param[in] path A path with a valid 8.3 DOS name for a file to be opened.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool open(SdBaseFile& dirFile, const char* path) { // NOLINT
|
||||||
|
return open(dirFile, path, O_RDWR);
|
||||||
|
}
|
||||||
|
/** \deprecated Use:
|
||||||
|
* bool open(SdBaseFile* dirFile, uint16_t index, uint8_t oflag);
|
||||||
|
* \param[in] dirFile An open SdFat instance for the directory.
|
||||||
|
* \param[in] index The \a index of the directory entry for the file to be
|
||||||
|
* opened. The value for \a index is (directory file position)/32.
|
||||||
|
* \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
|
||||||
|
* OR of flags O_READ, O_WRITE, O_TRUNC, and O_SYNC.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool open(SdBaseFile& dirFile, uint16_t index, uint8_t oflag) { // NOLINT
|
||||||
|
return open(&dirFile, index, oflag);
|
||||||
|
}
|
||||||
|
/** \deprecated Use: bool openRoot(SdVolume* vol);
|
||||||
|
* \param[in] vol The FAT volume containing the root directory to be opened.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool openRoot(SdVolume& vol) {return openRoot(&vol);} // NOLINT
|
||||||
|
/** \deprecated Use: int8_t readDir(dir_t* dir);
|
||||||
|
* \param[out] dir The dir_t struct that will receive the data.
|
||||||
|
* \return bytes read for success zero for eof or -1 for failure.
|
||||||
|
*/
|
||||||
|
int8_t readDir(dir_t& dir) {return readDir(&dir);} // NOLINT
|
||||||
|
/** \deprecated Use:
|
||||||
|
* static uint8_t remove(SdBaseFile* dirFile, const char* path);
|
||||||
|
* \param[in] dirFile The directory that contains the file.
|
||||||
|
* \param[in] path The name of the file to be removed.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
static bool remove(SdBaseFile& dirFile, const char* path) { // NOLINT
|
||||||
|
return remove(&dirFile, path);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// rest are private
|
||||||
|
private:
|
||||||
|
static void (*oldDateTime_)(uint16_t& date, uint16_t& time); // NOLINT
|
||||||
|
static void oldToNew(uint16_t* date, uint16_t* time) {
|
||||||
|
uint16_t d;
|
||||||
|
uint16_t t;
|
||||||
|
oldDateTime_(d, t);
|
||||||
|
*date = d;
|
||||||
|
*time = t;
|
||||||
|
}
|
||||||
|
#endif // ALLOW_DEPRECATED_FUNCTIONS
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SdBaseFile_h
|
@ -0,0 +1,329 @@
|
|||||||
|
/* Arduino SdFat Library
|
||||||
|
* Copyright (C) 2009 by William Greiman
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This Library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with the Arduino SdFat Library. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include "SdFat.h"
|
||||||
|
#include "SdFatUtil.h"
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Change a volume's working directory to root
|
||||||
|
*
|
||||||
|
* Changes the volume's working directory to the SD's root directory.
|
||||||
|
* Optionally set the current working directory to the volume's
|
||||||
|
* working directory.
|
||||||
|
*
|
||||||
|
* \param[in] set_cwd Set the current working directory to this volume's
|
||||||
|
* working directory if true.
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
*/
|
||||||
|
bool SdFat::chdir(bool set_cwd) {
|
||||||
|
if (set_cwd) SdBaseFile::cwd_ = &vwd_;
|
||||||
|
vwd_.close();
|
||||||
|
return vwd_.openRoot(&vol_);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Change a volume's working directory
|
||||||
|
*
|
||||||
|
* Changes the volume working directory to the \a path subdirectory.
|
||||||
|
* Optionally set the current working directory to the volume's
|
||||||
|
* working directory.
|
||||||
|
*
|
||||||
|
* Example: If the volume's working directory is "/DIR", chdir("SUB")
|
||||||
|
* will change the volume's working directory from "/DIR" to "/DIR/SUB".
|
||||||
|
*
|
||||||
|
* If path is "/", the volume's working directory will be changed to the
|
||||||
|
* root directory
|
||||||
|
*
|
||||||
|
* \param[in] path The name of the subdirectory.
|
||||||
|
*
|
||||||
|
* \param[in] set_cwd Set the current working directory to this volume's
|
||||||
|
* working directory if true.
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
*/
|
||||||
|
bool SdFat::chdir(const char *path, bool set_cwd) {
|
||||||
|
SdBaseFile 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;
|
||||||
|
vwd_ = dir;
|
||||||
|
if (set_cwd) SdBaseFile::cwd_ = &vwd_;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Set the current working directory to a volume's working directory.
|
||||||
|
*
|
||||||
|
* This is useful with multiple SD cards.
|
||||||
|
*
|
||||||
|
* The current working directory is changed to this volume's working directory.
|
||||||
|
*
|
||||||
|
* This is like the Windows/DOS \<drive letter>: command.
|
||||||
|
*/
|
||||||
|
void SdFat::chvol() {
|
||||||
|
SdBaseFile::cwd_ = &vwd_;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print any SD error code and halt. */
|
||||||
|
void SdFat::errorHalt() {
|
||||||
|
errorPrint();
|
||||||
|
while (1);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print msg, any SD error code, and halt.
|
||||||
|
*
|
||||||
|
* \param[in] msg Message to print.
|
||||||
|
*/
|
||||||
|
void SdFat::errorHalt(char const* msg) {
|
||||||
|
errorPrint(msg);
|
||||||
|
while (1);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print msg, any SD error code, and halt.
|
||||||
|
*
|
||||||
|
* \param[in] msg Message in program space (flash memory) to print.
|
||||||
|
*/
|
||||||
|
void SdFat::errorHalt_P(PGM_P msg) {
|
||||||
|
errorPrint_P(msg);
|
||||||
|
while (1);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print any SD error code. */
|
||||||
|
void SdFat::errorPrint() {
|
||||||
|
if (!card_.errorCode()) return;
|
||||||
|
PgmPrint("SD errorCode: 0X");
|
||||||
|
Serial.println(card_.errorCode(), HEX);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print msg, any SD error code.
|
||||||
|
*
|
||||||
|
* \param[in] msg Message to print.
|
||||||
|
*/
|
||||||
|
void SdFat::errorPrint(char const* msg) {
|
||||||
|
PgmPrint("error: ");
|
||||||
|
Serial.println(msg);
|
||||||
|
errorPrint();
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print msg, any SD error code.
|
||||||
|
*
|
||||||
|
* \param[in] msg Message in program space (flash memory) to print.
|
||||||
|
*/
|
||||||
|
void SdFat::errorPrint_P(PGM_P msg) {
|
||||||
|
PgmPrint("error: ");
|
||||||
|
SerialPrintln_P(msg);
|
||||||
|
errorPrint();
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Test for the existence of a file.
|
||||||
|
*
|
||||||
|
* \param[in] name Name of the file to be tested for.
|
||||||
|
*
|
||||||
|
* \return true if the file exists else false.
|
||||||
|
*/
|
||||||
|
bool SdFat::exists(const char* name) {
|
||||||
|
return vwd_.exists(name);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Initialize an SdFat object.
|
||||||
|
*
|
||||||
|
* Initializes the SD card, SD volume, and root directory.
|
||||||
|
*
|
||||||
|
* \param[in] sckRateID value for SPI SCK rate. See Sd2Card::init().
|
||||||
|
* \param[in] chipSelectPin SD chip select pin. See Sd2Card::init().
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure.
|
||||||
|
*/
|
||||||
|
bool SdFat::init(uint8_t sckRateID, uint8_t chipSelectPin) {
|
||||||
|
return card_.init(sckRateID, chipSelectPin) && vol_.init(&card_) && chdir(1);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print error details and halt after SdFat::init() fails. */
|
||||||
|
void SdFat::initErrorHalt() {
|
||||||
|
initErrorPrint();
|
||||||
|
while (1);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**Print message, error details, and halt after SdFat::init() fails.
|
||||||
|
*
|
||||||
|
* \param[in] msg Message to print.
|
||||||
|
*/
|
||||||
|
void SdFat::initErrorHalt(char const *msg) {
|
||||||
|
Serial.println(msg);
|
||||||
|
initErrorHalt();
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**Print message, error details, and halt after SdFat::init() fails.
|
||||||
|
*
|
||||||
|
* \param[in] msg Message in program space (flash memory) to print.
|
||||||
|
*/
|
||||||
|
void SdFat::initErrorHalt_P(PGM_P msg) {
|
||||||
|
SerialPrintln_P(msg);
|
||||||
|
initErrorHalt();
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Print error details after SdFat::init() fails. */
|
||||||
|
void SdFat::initErrorPrint() {
|
||||||
|
if (card_.errorCode()) {
|
||||||
|
PgmPrintln("Can't access SD card. Do not reformat.");
|
||||||
|
if (card_.errorCode() == SD_CARD_ERROR_CMD0) {
|
||||||
|
PgmPrintln("No card, wrong chip select pin, or SPI problem?");
|
||||||
|
}
|
||||||
|
errorPrint();
|
||||||
|
} else if (vol_.fatType() == 0) {
|
||||||
|
PgmPrintln("Invalid format, reformat SD.");
|
||||||
|
} else if (!vwd_.isOpen()) {
|
||||||
|
PgmPrintln("Can't open root directory.");
|
||||||
|
} else {
|
||||||
|
PgmPrintln("No error found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**Print message and error details and halt after SdFat::init() fails.
|
||||||
|
*
|
||||||
|
* \param[in] msg Message to print.
|
||||||
|
*/
|
||||||
|
void SdFat::initErrorPrint(char const *msg) {
|
||||||
|
Serial.println(msg);
|
||||||
|
initErrorPrint();
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**Print message and error details after SdFat::init() fails.
|
||||||
|
*
|
||||||
|
* \param[in] msg Message in program space (flash memory) to print.
|
||||||
|
*/
|
||||||
|
void SdFat::initErrorPrint_P(PGM_P msg) {
|
||||||
|
SerialPrintln_P(msg);
|
||||||
|
initErrorHalt();
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** List the directory contents of the volume working directory to Serial.
|
||||||
|
*
|
||||||
|
* \param[in] flags The inclusive OR of
|
||||||
|
*
|
||||||
|
* LS_DATE - %Print file modification date
|
||||||
|
*
|
||||||
|
* LS_SIZE - %Print file size.
|
||||||
|
*
|
||||||
|
* LS_R - Recursive list of subdirectories.
|
||||||
|
*/
|
||||||
|
void SdFat::ls(uint8_t flags) {
|
||||||
|
vwd_.ls(&Serial, flags);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** List the directory contents of the volume working directory to Serial.
|
||||||
|
*
|
||||||
|
* \param[in] pr Print stream for list.
|
||||||
|
*
|
||||||
|
* \param[in] flags The inclusive OR of
|
||||||
|
*
|
||||||
|
* LS_DATE - %Print file modification date
|
||||||
|
*
|
||||||
|
* LS_SIZE - %Print file size.
|
||||||
|
*
|
||||||
|
* LS_R - Recursive list of subdirectories.
|
||||||
|
*/
|
||||||
|
void SdFat::ls(Print* pr, uint8_t flags) {
|
||||||
|
vwd_.ls(pr, flags);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Make a subdirectory in the volume working directory.
|
||||||
|
*
|
||||||
|
* \param[in] path A path with a valid 8.3 DOS name for the subdirectory.
|
||||||
|
*
|
||||||
|
* \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.
|
||||||
|
*/
|
||||||
|
bool SdFat::mkdir(const char* path, bool pFlag) {
|
||||||
|
SdBaseFile sub;
|
||||||
|
return sub.mkdir(&vwd_, path, pFlag);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Remove a file from the volume working directory.
|
||||||
|
*
|
||||||
|
* \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.
|
||||||
|
*/
|
||||||
|
bool SdFat::remove(const char* path) {
|
||||||
|
return SdBaseFile::remove(&vwd_, path);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Rename a file or subdirectory.
|
||||||
|
*
|
||||||
|
* \param[in] oldPath Path name to the file or subdirectory to be renamed.
|
||||||
|
*
|
||||||
|
* \param[in] newPath New path name of the file or subdirectory.
|
||||||
|
*
|
||||||
|
* The \a newPath object must not exist before the rename call.
|
||||||
|
*
|
||||||
|
* The file to be renamed must not be open. The directory entry may be
|
||||||
|
* moved and file system corruption could occur if the file is accessed by
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
bool SdFat::rename(const char *oldPath, const char *newPath) {
|
||||||
|
SdBaseFile file;
|
||||||
|
if (!file.open(oldPath, O_READ)) return false;
|
||||||
|
return file.rename(&vwd_, newPath);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Remove a subdirectory from the volume's working directory.
|
||||||
|
*
|
||||||
|
* \param[in] path A path with a valid 8.3 DOS name for the subdirectory.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
bool SdFat::rmdir(const char* path) {
|
||||||
|
SdBaseFile sub;
|
||||||
|
if (!sub.open(path, O_READ)) return false;
|
||||||
|
return sub.rmdir();
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Truncate a file to a specified length. The current file position
|
||||||
|
* will be maintained if it is less than or equal to \a length otherwise
|
||||||
|
* it will be set to end of file.
|
||||||
|
*
|
||||||
|
* \param[in] path A path with a valid 8.3 DOS name 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.
|
||||||
|
*/
|
||||||
|
bool SdFat::truncate(const char* path, uint32_t length) {
|
||||||
|
SdBaseFile file;
|
||||||
|
if (!file.open(path, O_WRITE)) return false;
|
||||||
|
return file.truncate(length);
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
/* Arduino SdFat Library
|
||||||
|
* Copyright (C) 2009 by William Greiman
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This Library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with the Arduino SdFat Library. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* \file
|
||||||
|
* \brief configuration definitions
|
||||||
|
*/
|
||||||
|
#ifndef SdFatConfig_h
|
||||||
|
#define SdFatConfig_h
|
||||||
|
#include <stdint.h>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* To use multiple SD cards set USE_MULTIPLE_CARDS nonzero.
|
||||||
|
*
|
||||||
|
* Using multiple cards costs 400 - 500 bytes of flash.
|
||||||
|
*
|
||||||
|
* Each card requires about 550 bytes of SRAM so use of a Mega is recommended.
|
||||||
|
*/
|
||||||
|
#define USE_MULTIPLE_CARDS 0
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Call flush for endl if ENDL_CALLS_FLUSH is nonzero
|
||||||
|
*
|
||||||
|
* The standard for iostreams is to call flush. This is very costly for
|
||||||
|
* SdFat. Each call to flush causes 2048 bytes of I/O to the SD.
|
||||||
|
*
|
||||||
|
* SdFat has a single 512 byte buffer for SD I/O so it must write the current
|
||||||
|
* data block to the SD, read the directory block from the SD, update the
|
||||||
|
* directory entry, write the directory block to the SD and read the data
|
||||||
|
* block back into the buffer.
|
||||||
|
*
|
||||||
|
* The SD flash memory controller is not designed for this many rewrites
|
||||||
|
* so performance may be reduced by more than a factor of 100.
|
||||||
|
*
|
||||||
|
* If ENDL_CALLS_FLUSH is zero, you must call flush and/or close to force
|
||||||
|
* all data to be written to the SD.
|
||||||
|
*/
|
||||||
|
#define ENDL_CALLS_FLUSH 0
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Allow use of deprecated functions if ALLOW_DEPRECATED_FUNCTIONS is nonzero
|
||||||
|
*/
|
||||||
|
#define ALLOW_DEPRECATED_FUNCTIONS 1
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Allow FAT12 volumes if FAT12_SUPPORT is nonzero.
|
||||||
|
* FAT12 has not been well tested.
|
||||||
|
*/
|
||||||
|
#define FAT12_SUPPORT 0
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* SPI init rate for SD initialization commands. Must be 5 (F_CPU/64)
|
||||||
|
* or 6 (F_CPU/128).
|
||||||
|
*/
|
||||||
|
#define SPI_SD_INIT_RATE 5
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Set the SS pin high for hardware SPI. If SS is chip select for another SPI
|
||||||
|
* device this will disable that device during the SD init phase.
|
||||||
|
*/
|
||||||
|
#define SET_SPI_SS_HIGH 1
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Define MEGA_SOFT_SPI nonzero to use software SPI on Mega Arduinos.
|
||||||
|
* Pins used are SS 10, MOSI 11, MISO 12, and SCK 13.
|
||||||
|
*
|
||||||
|
* MEGA_SOFT_SPI allows an unmodified Adafruit GPS Shield to be used
|
||||||
|
* on Mega Arduinos. Software SPI works well with GPS Shield V1.1
|
||||||
|
* but many SD cards will fail with GPS Shield V1.0.
|
||||||
|
*/
|
||||||
|
#define MEGA_SOFT_SPI 0
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Set USE_SOFTWARE_SPI nonzero to always use software SPI.
|
||||||
|
*/
|
||||||
|
#define USE_SOFTWARE_SPI 0
|
||||||
|
// define software SPI pins so Mega can use unmodified 168/328 shields
|
||||||
|
/** Software SPI chip select pin for the SD */
|
||||||
|
uint8_t const SOFT_SPI_CS_PIN = 10;
|
||||||
|
/** 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;
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* The __cxa_pure_virtual function is an error handler that is invoked when
|
||||||
|
* a pure virtual function is called.
|
||||||
|
*/
|
||||||
|
#define USE_CXA_PURE_VIRTUAL 1
|
||||||
|
#endif // SdFatConfig_h
|
@ -0,0 +1,74 @@
|
|||||||
|
/* Arduino SdFat Library
|
||||||
|
* Copyright (C) 2008 by William Greiman
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This Library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with the Arduino SdFat Library. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include "SdFatUtil.h"
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** Amount of free RAM
|
||||||
|
* \return The number of free bytes.
|
||||||
|
*/
|
||||||
|
int SdFatUtil::FreeRam() {
|
||||||
|
extern int __bss_end;
|
||||||
|
extern int* __brkval;
|
||||||
|
int free_memory;
|
||||||
|
if (reinterpret_cast<int>(__brkval) == 0) {
|
||||||
|
// if no heap use from end of bss section
|
||||||
|
free_memory = reinterpret_cast<int>(&free_memory)
|
||||||
|
- reinterpret_cast<int>(&__bss_end);
|
||||||
|
} else {
|
||||||
|
// use from top of stack to heap
|
||||||
|
free_memory = reinterpret_cast<int>(&free_memory)
|
||||||
|
- reinterpret_cast<int>(__brkval);
|
||||||
|
}
|
||||||
|
return free_memory;
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %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) {
|
||||||
|
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) {
|
||||||
|
print_P(pr, str);
|
||||||
|
pr->println();
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print a string in flash memory to Serial.
|
||||||
|
*
|
||||||
|
* \param[in] str Pointer to string stored in flash memory.
|
||||||
|
*/
|
||||||
|
void SdFatUtil::SerialPrint_P(PGM_P str) {
|
||||||
|
print_P(&Serial, str);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/** %Print a string in flash memory to Serial followed by a CR/LF.
|
||||||
|
*
|
||||||
|
* \param[in] str Pointer to string stored in flash memory.
|
||||||
|
*/
|
||||||
|
void SdFatUtil::SerialPrintln_P(PGM_P str) {
|
||||||
|
println_P(&Serial, str);
|
||||||
|
}
|
@ -1,202 +0,0 @@
|
|||||||
/* Arduino SdFat Library
|
|
||||||
* Copyright (C) 2009 by William Greiman
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This Library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with the Arduino SdFat Library. If not, see
|
|
||||||
* <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
\mainpage Arduino SdFat Library
|
|
||||||
<CENTER>Copyright © 2009 by William Greiman
|
|
||||||
</CENTER>
|
|
||||||
|
|
||||||
\section Intro Introduction
|
|
||||||
The Arduino SdFat Library is a minimal implementation of FAT16 and FAT32
|
|
||||||
file systems on SD flash memory cards. Standard SD and high capacity
|
|
||||||
SDHC cards are supported.
|
|
||||||
|
|
||||||
The SdFat only supports short 8.3 names.
|
|
||||||
|
|
||||||
The main classes in SdFat are Sd2Card, SdVolume, and SdFile.
|
|
||||||
|
|
||||||
The Sd2Card class supports access to standard SD cards and SDHC cards. Most
|
|
||||||
applications will only need to call the Sd2Card::init() member function.
|
|
||||||
|
|
||||||
The SdVolume class supports FAT16 and FAT32 partitions. Most applications
|
|
||||||
will only need to call the SdVolume::init() member function.
|
|
||||||
|
|
||||||
The SdFile class provides file access functions such as open(), read(),
|
|
||||||
remove(), write(), close() and sync(). This class supports access to the root
|
|
||||||
directory and subdirectories.
|
|
||||||
|
|
||||||
A number of example are provided in the SdFat/examples folder. These were
|
|
||||||
developed to test SdFat and illustrate its use.
|
|
||||||
|
|
||||||
SdFat was developed for high speed data recording. SdFat was used to implement
|
|
||||||
an audio record/play class, WaveRP, for the Adafruit Wave Shield. This
|
|
||||||
application uses special Sd2Card calls to write to contiguous files in raw mode.
|
|
||||||
These functions reduce write latency so that audio can be recorded with the
|
|
||||||
small amount of RAM in the Arduino.
|
|
||||||
|
|
||||||
\section SDcard SD\SDHC Cards
|
|
||||||
|
|
||||||
Arduinos access SD cards using the cards SPI protocol. PCs, Macs, and
|
|
||||||
most consumer devices use the 4-bit parallel SD protocol. A card that
|
|
||||||
functions well on A PC or Mac may not work well on the Arduino.
|
|
||||||
|
|
||||||
Most cards have good SPI read performance but cards vary widely in SPI
|
|
||||||
write performance. Write performance is limited by how efficiently the
|
|
||||||
card manages internal erase/remapping operations. The Arduino cannot
|
|
||||||
optimize writes to reduce erase operations because of its limit RAM.
|
|
||||||
|
|
||||||
SanDisk cards generally have good write performance. They seem to have
|
|
||||||
more internal RAM buffering than other cards and therefore can limit
|
|
||||||
the number of flash erase operations that the Arduino forces due to its
|
|
||||||
limited RAM.
|
|
||||||
|
|
||||||
\section Hardware Hardware Configuration
|
|
||||||
|
|
||||||
SdFat was developed using an
|
|
||||||
<A HREF = "http://www.adafruit.com/"> Adafruit Industries</A>
|
|
||||||
<A HREF = "http://www.ladyada.net/make/waveshield/"> Wave Shield</A>.
|
|
||||||
|
|
||||||
The hardware interface to the SD card should not use a resistor based level
|
|
||||||
shifter. SdFat sets the SPI bus frequency to 8 MHz which results in signal
|
|
||||||
rise times that are too slow for the edge detectors in many newer SD card
|
|
||||||
controllers when resistor voltage dividers are used.
|
|
||||||
|
|
||||||
The 5 to 3.3 V level shifter for 5 V Arduinos should be IC based like the
|
|
||||||
74HC4050N based circuit shown in the file SdLevel.png. The Adafruit Wave Shield
|
|
||||||
uses a 74AHC125N. Gravitech sells SD and MicroSD Card Adapters based on the
|
|
||||||
74LCX245.
|
|
||||||
|
|
||||||
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
|
|
||||||
card.init(SPI_HALF_SPEED) to initialize the SD card.
|
|
||||||
|
|
||||||
\section comment Bugs and Comments
|
|
||||||
|
|
||||||
If you wish to report bugs or have comments, send email to fat16lib@sbcglobal.net.
|
|
||||||
|
|
||||||
\section SdFatClass SdFat Usage
|
|
||||||
|
|
||||||
SdFat uses a slightly restricted form of short names.
|
|
||||||
Only printable ASCII characters are supported. No characters with code point
|
|
||||||
values greater than 127 are allowed. Space is not allowed even though space
|
|
||||||
was allowed in the API of early versions of DOS.
|
|
||||||
|
|
||||||
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
|
|
||||||
of letters and digits. The following special characters are also allowed:
|
|
||||||
|
|
||||||
$ % ' - _ @ ~ ` ! ( ) { } ^ # &
|
|
||||||
|
|
||||||
Short names are always converted to upper case and their original case
|
|
||||||
value is lost.
|
|
||||||
|
|
||||||
\note
|
|
||||||
The Arduino Print class uses character
|
|
||||||
at a time writes so it was necessary to use a \link SdFile::sync() sync() \endlink
|
|
||||||
function to control when data is written to the SD card.
|
|
||||||
|
|
||||||
\par
|
|
||||||
An application which writes to a file using \link Print::print() print()\endlink,
|
|
||||||
\link Print::println() println() \endlink
|
|
||||||
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.
|
|
||||||
|
|
||||||
\par
|
|
||||||
Applications must use care calling \link SdFile::sync() sync() \endlink
|
|
||||||
since 2048 bytes of I/O is required to update file and
|
|
||||||
directory information. This includes writing the current data block, reading
|
|
||||||
the block that contains the directory entry for update, writing the directory
|
|
||||||
block back and reading back the current data block.
|
|
||||||
|
|
||||||
It is possible to open a file with two or more instances of SdFile. A file may
|
|
||||||
be corrupted if data is written to the file by more than one instance of SdFile.
|
|
||||||
|
|
||||||
\section HowTo How to format SD Cards as FAT Volumes
|
|
||||||
|
|
||||||
You should use a freshly formatted SD card for best performance. FAT
|
|
||||||
file systems become slower if many files have been created and deleted.
|
|
||||||
This is because the directory entry for a deleted file is marked as deleted,
|
|
||||||
but is not deleted. When a new file is created, these entries must be scanned
|
|
||||||
before creating the file, a flaw in the FAT design. Also files can become
|
|
||||||
fragmented which causes reads and writes to be slower.
|
|
||||||
|
|
||||||
Microsoft operating systems support removable media formatted with a
|
|
||||||
Master Boot Record, MBR, or formatted as a super floppy with a FAT Boot Sector
|
|
||||||
in block zero.
|
|
||||||
|
|
||||||
Microsoft operating systems expect MBR formatted removable media
|
|
||||||
to have only one partition. The first partition should be used.
|
|
||||||
|
|
||||||
Microsoft operating systems do not support partitioning SD flash cards.
|
|
||||||
If you erase an SD card with a program like KillDisk, Most versions of
|
|
||||||
Windows will format the card as a super floppy.
|
|
||||||
|
|
||||||
The best way to restore an SD card's format is to use SDFormatter
|
|
||||||
which can be downloaded from:
|
|
||||||
|
|
||||||
http://www.sdcard.org/consumers/formatter/
|
|
||||||
|
|
||||||
SDFormatter aligns flash erase boundaries with file
|
|
||||||
system structures which reduces write latency and file system overhead.
|
|
||||||
|
|
||||||
SDFormatter does not have an option for FAT type so it may format
|
|
||||||
small cards as FAT12.
|
|
||||||
|
|
||||||
After the MBR is restored by SDFormatter you may need to reformat small
|
|
||||||
cards that have been formatted FAT12 to force the volume type to be FAT16.
|
|
||||||
|
|
||||||
If you reformat the SD card with an OS utility, choose a cluster size that
|
|
||||||
will result in:
|
|
||||||
|
|
||||||
4084 < CountOfClusters && CountOfClusters < 65525
|
|
||||||
|
|
||||||
The volume will then be FAT16.
|
|
||||||
|
|
||||||
If you are formatting an SD card on OS X or Linux, be sure to use the first
|
|
||||||
partition. Format this partition with a cluster count in above range.
|
|
||||||
|
|
||||||
\section References References
|
|
||||||
|
|
||||||
Adafruit Industries:
|
|
||||||
|
|
||||||
http://www.adafruit.com/
|
|
||||||
|
|
||||||
http://www.ladyada.net/make/waveshield/
|
|
||||||
|
|
||||||
The Arduino site:
|
|
||||||
|
|
||||||
http://www.arduino.cc/
|
|
||||||
|
|
||||||
For more information about FAT file systems see:
|
|
||||||
|
|
||||||
http://www.microsoft.com/whdc/system/platform/firmware/fatgen.mspx
|
|
||||||
|
|
||||||
For information about using SD cards as SPI devices see:
|
|
||||||
|
|
||||||
http://www.sdcard.org/developers/tech/sdcard/pls/Simplified_Physical_Layer_Spec.pdf
|
|
||||||
|
|
||||||
The ATmega328 datasheet:
|
|
||||||
|
|
||||||
http://www.atmel.com/dyn/resources/prod_documents/doc8161.pdf
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
@ -0,0 +1,42 @@
|
|||||||
|
/* Arduino SdFat Library
|
||||||
|
* Copyright (C) 2009 by William Greiman
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This Library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with the Arduino SdFat Library. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* \file
|
||||||
|
* \brief SdFile class
|
||||||
|
*/
|
||||||
|
#include "SdBaseFile.h"
|
||||||
|
#ifndef SdFile_h
|
||||||
|
#define SdFile_h
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* \class SdFile
|
||||||
|
* \brief SdBaseFile with Print.
|
||||||
|
*/
|
||||||
|
class SdFile : public SdBaseFile, public Print {
|
||||||
|
public:
|
||||||
|
SdFile() {}
|
||||||
|
SdFile(const char* name, uint8_t oflag);
|
||||||
|
void write(uint8_t b);
|
||||||
|
int16_t write(const void* buf, uint16_t nbyte);
|
||||||
|
void write(const char* str);
|
||||||
|
void write_P(PGM_P str);
|
||||||
|
void writeln_P(PGM_P str);
|
||||||
|
};
|
||||||
|
#endif // SdFile_h
|
@ -0,0 +1,211 @@
|
|||||||
|
/* Arduino SdFat Library
|
||||||
|
* Copyright (C) 2009 by William Greiman
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This Library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with the Arduino SdFat Library. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifndef SdVolume_h
|
||||||
|
#define SdVolume_h
|
||||||
|
/**
|
||||||
|
* \file
|
||||||
|
* \brief SdVolume class
|
||||||
|
*/
|
||||||
|
#include "SdFatConfig.h"
|
||||||
|
#include "Sd2Card.h"
|
||||||
|
#include "SdFatStructs.h"
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// SdVolume class
|
||||||
|
/**
|
||||||
|
* \brief Cache for an SD data block
|
||||||
|
*/
|
||||||
|
union cache_t {
|
||||||
|
/** Used to access cached file data blocks. */
|
||||||
|
uint8_t data[512];
|
||||||
|
/** Used to access cached FAT16 entries. */
|
||||||
|
uint16_t fat16[256];
|
||||||
|
/** Used to access cached FAT32 entries. */
|
||||||
|
uint32_t fat32[128];
|
||||||
|
/** Used to access cached directory entries. */
|
||||||
|
dir_t dir[16];
|
||||||
|
/** Used to access a cached Master Boot Record. */
|
||||||
|
mbr_t mbr;
|
||||||
|
/** Used to access to a cached FAT boot sector. */
|
||||||
|
fat_boot_t fbs;
|
||||||
|
/** Used to access to a cached FAT32 boot sector. */
|
||||||
|
fat32_boot_t fbs32;
|
||||||
|
/** Used to access to a cached FAT32 FSINFO sector. */
|
||||||
|
fat32_fsinfo_t fsinfo;
|
||||||
|
};
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* \class SdVolume
|
||||||
|
* \brief Access FAT16 and FAT32 volumes on SD and SDHC cards.
|
||||||
|
*/
|
||||||
|
class SdVolume {
|
||||||
|
public:
|
||||||
|
/** Create an instance of SdVolume */
|
||||||
|
SdVolume() : fatType_(0) {}
|
||||||
|
/** Clear the cache and returns a pointer to the cache. Used by the WaveRP
|
||||||
|
* recorder to do raw write to the SD card. Not for normal apps.
|
||||||
|
* \return A pointer to the cache buffer or zero if an error occurs.
|
||||||
|
*/
|
||||||
|
cache_t* cacheClear() {
|
||||||
|
if (!cacheFlush()) return 0;
|
||||||
|
cacheBlockNumber_ = 0XFFFFFFFF;
|
||||||
|
return &cacheBuffer_;
|
||||||
|
}
|
||||||
|
/** Initialize a FAT volume. Try partition one first then try super
|
||||||
|
* floppy format.
|
||||||
|
*
|
||||||
|
* \param[in] dev The Sd2Card where the volume is located.
|
||||||
|
*
|
||||||
|
* \return The value one, true, is returned for success and
|
||||||
|
* the value zero, false, is returned for failure. Reasons for
|
||||||
|
* failure include not finding a valid partition, not finding a valid
|
||||||
|
* FAT file system or an I/O error.
|
||||||
|
*/
|
||||||
|
bool init(Sd2Card* dev) { return init(dev, 1) ? true : init(dev, 0);}
|
||||||
|
bool init(Sd2Card* dev, uint8_t part);
|
||||||
|
|
||||||
|
// inline functions that return volume info
|
||||||
|
/** \return The volume's cluster size in blocks. */
|
||||||
|
uint8_t blocksPerCluster() const {return blocksPerCluster_;}
|
||||||
|
/** \return The number of blocks in one FAT. */
|
||||||
|
uint32_t blocksPerFat() const {return blocksPerFat_;}
|
||||||
|
/** \return The total number of clusters in the volume. */
|
||||||
|
uint32_t clusterCount() const {return clusterCount_;}
|
||||||
|
/** \return The shift count required to multiply by blocksPerCluster. */
|
||||||
|
uint8_t clusterSizeShift() const {return clusterSizeShift_;}
|
||||||
|
/** \return The logical block number for the start of file data. */
|
||||||
|
uint32_t dataStartBlock() const {return dataStartBlock_;}
|
||||||
|
/** \return The number of FAT structures on the volume. */
|
||||||
|
uint8_t fatCount() const {return fatCount_;}
|
||||||
|
/** \return The logical block number for the start of the first FAT. */
|
||||||
|
uint32_t fatStartBlock() const {return fatStartBlock_;}
|
||||||
|
/** \return The FAT type of the volume. Values are 12, 16 or 32. */
|
||||||
|
uint8_t fatType() const {return fatType_;}
|
||||||
|
int32_t freeClusterCount();
|
||||||
|
/** \return The number of entries in the root directory for FAT16 volumes. */
|
||||||
|
uint32_t rootDirEntryCount() const {return rootDirEntryCount_;}
|
||||||
|
/** \return The logical block number for the start of the root directory
|
||||||
|
on FAT16 volumes or the first cluster number on FAT32 volumes. */
|
||||||
|
uint32_t rootDirStart() const {return rootDirStart_;}
|
||||||
|
/** Sd2Card object for this volume
|
||||||
|
* \return pointer to Sd2Card object.
|
||||||
|
*/
|
||||||
|
Sd2Card* sdCard() {return sdCard_;}
|
||||||
|
/** Debug access to FAT table
|
||||||
|
*
|
||||||
|
* \param[in] n cluster number.
|
||||||
|
* \param[out] v value of entry
|
||||||
|
* \return true for success or false for failure
|
||||||
|
*/
|
||||||
|
bool dbgFat(uint32_t n, uint32_t* v) {return fatGet(n, v);}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
private:
|
||||||
|
// Allow SdBaseFile access to SdVolume private data.
|
||||||
|
friend class SdBaseFile;
|
||||||
|
|
||||||
|
// value for dirty argument in cacheRawBlock to indicate read from cache
|
||||||
|
static bool const CACHE_FOR_READ = false;
|
||||||
|
// value for dirty argument in cacheRawBlock to indicate write to cache
|
||||||
|
static bool const CACHE_FOR_WRITE = true;
|
||||||
|
|
||||||
|
#if USE_MULTIPLE_CARDS
|
||||||
|
cache_t cacheBuffer_; // 512 byte cache for device blocks
|
||||||
|
uint32_t cacheBlockNumber_; // Logical number of block in the cache
|
||||||
|
Sd2Card* sdCard_; // Sd2Card object for cache
|
||||||
|
bool cacheDirty_; // cacheFlush() will write block if true
|
||||||
|
uint32_t cacheMirrorBlock_; // block number for mirror FAT
|
||||||
|
#else // USE_MULTIPLE_CARDS
|
||||||
|
static cache_t cacheBuffer_; // 512 byte cache for device blocks
|
||||||
|
static uint32_t cacheBlockNumber_; // Logical number of block in the cache
|
||||||
|
static Sd2Card* sdCard_; // Sd2Card object for cache
|
||||||
|
static bool cacheDirty_; // cacheFlush() will write block if true
|
||||||
|
static uint32_t cacheMirrorBlock_; // block number for mirror FAT
|
||||||
|
#endif // USE_MULTIPLE_CARDS
|
||||||
|
uint32_t allocSearchStart_; // start cluster for alloc search
|
||||||
|
uint8_t blocksPerCluster_; // cluster size in blocks
|
||||||
|
uint32_t blocksPerFat_; // FAT size in blocks
|
||||||
|
uint32_t clusterCount_; // clusters in one FAT
|
||||||
|
uint8_t clusterSizeShift_; // shift to convert cluster count to block count
|
||||||
|
uint32_t dataStartBlock_; // first data block number
|
||||||
|
uint8_t fatCount_; // number of FATs on volume
|
||||||
|
uint32_t fatStartBlock_; // start block for first FAT
|
||||||
|
uint8_t fatType_; // volume type (12, 16, OR 32)
|
||||||
|
uint16_t rootDirEntryCount_; // number of entries in FAT16 root dir
|
||||||
|
uint32_t rootDirStart_; // root start block for FAT16, cluster for FAT32
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
bool allocContiguous(uint32_t count, uint32_t* curCluster);
|
||||||
|
uint8_t blockOfCluster(uint32_t position) const {
|
||||||
|
return (position >> 9) & (blocksPerCluster_ - 1);}
|
||||||
|
uint32_t clusterStartBlock(uint32_t cluster) const {
|
||||||
|
return dataStartBlock_ + ((cluster - 2) << clusterSizeShift_);}
|
||||||
|
uint32_t blockNumber(uint32_t cluster, uint32_t position) const {
|
||||||
|
return clusterStartBlock(cluster) + blockOfCluster(position);}
|
||||||
|
cache_t *cache() {return &cacheBuffer_;}
|
||||||
|
uint32_t cacheBlockNumber() {return cacheBlockNumber_;}
|
||||||
|
#if USE_MULTIPLE_CARDS
|
||||||
|
bool cacheFlush();
|
||||||
|
bool cacheRawBlock(uint32_t blockNumber, bool dirty);
|
||||||
|
#else // USE_MULTIPLE_CARDS
|
||||||
|
static bool cacheFlush();
|
||||||
|
static bool cacheRawBlock(uint32_t blockNumber, bool dirty);
|
||||||
|
#endif // USE_MULTIPLE_CARDS
|
||||||
|
// used by SdBaseFile write to assign cache to SD location
|
||||||
|
void cacheSetBlockNumber(uint32_t blockNumber, bool dirty) {
|
||||||
|
cacheDirty_ = dirty;
|
||||||
|
cacheBlockNumber_ = blockNumber;
|
||||||
|
}
|
||||||
|
void cacheSetDirty() {cacheDirty_ |= CACHE_FOR_WRITE;}
|
||||||
|
bool chainSize(uint32_t beginCluster, uint32_t* size);
|
||||||
|
bool fatGet(uint32_t cluster, uint32_t* value);
|
||||||
|
bool fatPut(uint32_t cluster, uint32_t value);
|
||||||
|
bool fatPutEOC(uint32_t cluster) {
|
||||||
|
return fatPut(cluster, 0x0FFFFFFF);
|
||||||
|
}
|
||||||
|
bool freeChain(uint32_t cluster);
|
||||||
|
bool isEOC(uint32_t cluster) const {
|
||||||
|
if (FAT12_SUPPORT && fatType_ == 12) return cluster >= FAT12EOC_MIN;
|
||||||
|
if (fatType_ == 16) return cluster >= FAT16EOC_MIN;
|
||||||
|
return cluster >= FAT32EOC_MIN;
|
||||||
|
}
|
||||||
|
bool readBlock(uint32_t block, uint8_t* dst) {
|
||||||
|
return sdCard_->readBlock(block, dst);}
|
||||||
|
bool writeBlock(uint32_t block, const uint8_t* dst) {
|
||||||
|
return sdCard_->writeBlock(block, dst);
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Deprecated functions - suppress cpplint warnings with NOLINT comment
|
||||||
|
#if ALLOW_DEPRECATED_FUNCTIONS && !defined(DOXYGEN)
|
||||||
|
public:
|
||||||
|
/** \deprecated Use: bool SdVolume::init(Sd2Card* dev);
|
||||||
|
* \param[in] dev The SD card where the volume is located.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool init(Sd2Card& dev) {return init(&dev);} // NOLINT
|
||||||
|
/** \deprecated Use: bool SdVolume::init(Sd2Card* dev, uint8_t vol);
|
||||||
|
* \param[in] dev The SD card where the volume is located.
|
||||||
|
* \param[in] part The partition to be used.
|
||||||
|
* \return true for success or false for failure.
|
||||||
|
*/
|
||||||
|
bool init(Sd2Card& dev, uint8_t part) { // NOLINT
|
||||||
|
return init(&dev, part);
|
||||||
|
}
|
||||||
|
#endif // ALLOW_DEPRECATED_FUNCTIONS
|
||||||
|
};
|
||||||
|
#endif // SdVolume
|
@ -1,58 +0,0 @@
|
|||||||
files to compare manually:
|
|
||||||
planner.cpp
|
|
||||||
stepper.cpp
|
|
||||||
temperature.cpp
|
|
||||||
|
|
||||||
---
|
|
||||||
things that changed:
|
|
||||||
* planner.cpp
|
|
||||||
estimate_acc_distance now works with floats.
|
|
||||||
in calculate_trapezoid:for_block
|
|
||||||
long acceleration_rate=(long)((float)acceleration*8.388608) is gone
|
|
||||||
so is block_>acceleration_rate
|
|
||||||
void planner_reverse_pass:
|
|
||||||
some stuff I don't understand right now changed
|
|
||||||
in planner_forward_pass:
|
|
||||||
done: BLOCK_BUFFER_SIZE is now necessarily power of 2 (aka 8 16, 32). Inportant to document this somewhere.
|
|
||||||
no more inline in void plan_discard_current_block()
|
|
||||||
no more inline in plan_get_current_block()
|
|
||||||
in plan_buffer_line(...)
|
|
||||||
the long target[4]; and calculations of thoose should go after the while(block_buffer_tail==..). if the axis_steps_per_unit are changed from the gcode (M92) the calculation for the currently planned buffer move will be corrupt, because Target is calculated with one value, and the stuff afterwards with another. At least this solved the problem I had with the M92 E* changes in the code. Very sure about this, I took me 20min to find this as the solution for the bug I was hunting.
|
|
||||||
around if(feed_rate<minimumfeedrate) this only should be done if it is not a pure extrusion. I think there is a bug right now.
|
|
||||||
~line 447 blockcount=
|
|
||||||
not sure if this also works if the difference is negative, as it would happen if the ringbuffer runs over the end and start at 0.
|
|
||||||
~line 507 tmp_aceleration. not sure whats going on, but a lot changed.
|
|
||||||
|
|
||||||
|
|
||||||
* stepper.cpp
|
|
||||||
~214: if (busy) should be a echoln, maybe
|
|
||||||
~331: great, The Z_M_PIN checks are in :)
|
|
||||||
|
|
||||||
*temperature.cpp
|
|
||||||
done: enum for heater, bed,
|
|
||||||
manage_heater() is seriously different.
|
|
||||||
done: if tem_meas_ready ==true->!true+return?
|
|
||||||
done #define K1 0.95 maybe in the configuration.h?
|
|
||||||
semi-done: PID-C checking needed. Untested but added.
|
|
||||||
----
|
|
||||||
|
|
||||||
still needed to finish the merge, before testin!
|
|
||||||
|
|
||||||
manage_heater
|
|
||||||
ISR
|
|
||||||
movement planner
|
|
||||||
|
|
||||||
TODO:
|
|
||||||
|
|
||||||
remove traveling at maxpseed
|
|
||||||
remove Simplelcd
|
|
||||||
|
|
||||||
remove DEBUG_STEPS?
|
|
||||||
|
|
||||||
block_t
|
|
||||||
pid_dt ->0.1 whats the changes to the PID, checking needed
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
second merge saturday morning:
|
|
||||||
done: PID_dt->0.1
|
|
Loading…
Reference in new issue