4494 lines
128 KiB
C++
4494 lines
128 KiB
C++
/* Arduino SdFat Library
|
|
* Copyright (C) 2012 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 "Repetier.h"
|
|
#if SDSUPPORT
|
|
#if defined(ARDUINO) && ARDUINO >= 100
|
|
#include "Arduino.h"
|
|
#else
|
|
#include "WProgram.h"
|
|
#define COMPAT_PRE1
|
|
#endif
|
|
//#include <SdFat.h>
|
|
|
|
extern int8_t RFstricmp(const char* s1, const char* s2);
|
|
extern int8_t RFstrnicmp(const char* s1, const char* s2, size_t n);
|
|
|
|
//#define GLENN_DEBUG
|
|
|
|
//------------------------------------------------------------------------------
|
|
static void pstrPrintln(FSTRINGPARAM(str)) {
|
|
Com::printFLN(str);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/**
|
|
* Initialize an SdFat object.
|
|
*
|
|
* Initializes the SD card, SD volume, and root directory.
|
|
*
|
|
* \param[in] chipSelectPin SD chip select pin. See Sd2Card::init().
|
|
* \param[in] sckRateID value for SPI SCK rate. See Sd2Card::init().
|
|
*
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool SdFat::begin(uint8_t chipSelectPin, uint8_t sckRateID) {
|
|
return card_.init(sckRateID, chipSelectPin) && vol_.init(&card_) && chdir(1);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
|
|
/** 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_;
|
|
if (vwd_.isOpen()) 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(FSTRINGPARAM(msg)) {
|
|
errorPrint_P(msg);
|
|
while (1);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** %Print any SD error code. */
|
|
void SdFat::errorPrint() {
|
|
if (!card_.errorCode()) return;
|
|
Com::printFLN(Com::tSDErrorCode,card_.errorCode());
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** %Print msg, any SD error code.
|
|
*
|
|
* \param[in] msg Message to print.
|
|
*/
|
|
void SdFat::errorPrint(char const* msg) {
|
|
Com::printFLN(Com::tError,msg);
|
|
errorPrint();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** %Print msg, any SD error code.
|
|
*
|
|
* \param[in] msg Message in program space (flash memory) to print.
|
|
*/
|
|
void SdFat::errorPrint_P(FSTRINGPARAM(msg)) {
|
|
Com::printF(Com::tError);
|
|
Com::printFLN(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);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** %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) {
|
|
Com::print(msg);
|
|
Com::println();
|
|
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(FSTRINGPARAM(msg)) {
|
|
pstrPrintln(msg);
|
|
initErrorHalt();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Print error details after SdFat::init() fails. */
|
|
void SdFat::initErrorPrint() {
|
|
if (card_.errorCode()) {
|
|
pstrPrintln(PSTR("Can't access SD card. Do not reformat."));
|
|
if (card_.errorCode() == SD_CARD_ERROR_CMD0) {
|
|
pstrPrintln(PSTR("No card, wrong chip select pin, or SPI problem?"));
|
|
}
|
|
errorPrint();
|
|
} else if (vol_.fatType() == 0) {
|
|
pstrPrintln(PSTR("Invalid format, reformat SD."));
|
|
} else if (!vwd_.isOpen()) {
|
|
pstrPrintln(PSTR("Can't open root directory."));
|
|
} else {
|
|
pstrPrintln(PSTR("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) {
|
|
Com::print(msg);
|
|
Com::println();
|
|
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(FSTRINGPARAM(msg)) {
|
|
pstrPrintln(msg);
|
|
initErrorHalt();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** 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);
|
|
}
|
|
|
|
// macro for debug
|
|
#define DBG_FAIL_MACRO // Serial.println(__LINE__)
|
|
//------------------------------------------------------------------------------
|
|
// 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_)) {
|
|
DBG_FAIL_MACRO;
|
|
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
|
|
cache_t* SdBaseFile::addDirCluster() {
|
|
uint32_t block;
|
|
cache_t* pc;
|
|
// max folder size
|
|
if (fileSize_/sizeof(dir_t) >= 0XFFFF) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (!addCluster()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
block = vol_->clusterStartBlock(curCluster_);
|
|
pc = vol_->cacheFetch(block, SdVolume::CACHE_RESERVE_FOR_WRITE);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
memset(pc, 0, 512);
|
|
// zero rest of clusters
|
|
for (uint8_t i = 1; i < vol_->blocksPerCluster_; i++) {
|
|
if (!vol_->writeBlock(block + i, pc->data)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
// Increase directory file size by cluster size
|
|
fileSize_ += 512UL*vol_->blocksPerCluster_;
|
|
return pc;
|
|
|
|
fail:
|
|
return 0;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
// cache a file's directory entry
|
|
// return pointer to cached entry or null for failure
|
|
dir_t* SdBaseFile::cacheDirEntry(uint8_t action) {
|
|
cache_t* pc;
|
|
pc = vol_->cacheFetch(dirBlock_, action);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
return pc->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) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
for (uint32_t c = firstCluster_; ; c++) {
|
|
uint32_t next;
|
|
if (!vol_->fatGet(c, &next)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// check for contiguous
|
|
if (next != (c + 1)) {
|
|
// error if not end of chain
|
|
if (!vol_->isEOC(next)) {
|
|
DBG_FAIL_MACRO;
|
|
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) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (!open(dirFile, path, O_CREAT | O_EXCL | O_RDWR)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// calculate number of clusters needed
|
|
count = ((size - 1) >> (vol_->clusterSizeShift_ + 9)) + 1;
|
|
|
|
// allocate clusters
|
|
if (!vol_->allocContiguous(count, &firstCluster_)) {
|
|
remove();
|
|
DBG_FAIL_MACRO;
|
|
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()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// read entry
|
|
p = cacheDirEntry(SdVolume::CACHE_FOR_READ);
|
|
if (!p) {
|
|
DBG_FAIL_MACRO;
|
|
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) {
|
|
dir_t* p;
|
|
if (!isOpen()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (isRoot()) {
|
|
name[0] = '/';
|
|
name[1] = '\0';
|
|
return true;
|
|
}
|
|
// cache entry
|
|
p = cacheDirEntry(SdVolume::CACHE_FOR_READ);
|
|
if (!p) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// format name
|
|
dirName(*p, name);
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void SdBaseFile::getpos(FatPos_t* pos) {
|
|
pos->position = curPosition_;
|
|
pos->cluster = curCluster_;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** List directory contents to stdOut.
|
|
*
|
|
* \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(flags, 0);
|
|
}
|
|
|
|
uint8_t SdBaseFile::lsRecursive(SdBaseFile *parent, uint8_t level, char *findFilename, SdBaseFile *pParentFound, bool isJson)
|
|
{
|
|
dir_t *p = NULL;
|
|
//uint8_t cnt=0;
|
|
//char *oldpathend = pathend;
|
|
bool firstFile = true;
|
|
|
|
parent->rewind();
|
|
|
|
while ((p = parent->getLongFilename(p, tempLongFilename, 0, NULL)))
|
|
{
|
|
HAL::pingWatchdog();
|
|
if (! (DIR_IS_FILE(p) || DIR_IS_SUBDIR(p))) continue;
|
|
if (strcmp(tempLongFilename, "..") == 0) continue;
|
|
if (tempLongFilename[0] == '.') continue; // MAC CRAP
|
|
if (DIR_IS_SUBDIR(p)) {
|
|
if (level >= SD_MAX_FOLDER_DEPTH) continue; // can't go deeper
|
|
if (level < SD_MAX_FOLDER_DEPTH && findFilename == NULL) {
|
|
if (level && !isJson) {
|
|
Com::print(fullName);
|
|
Com::printF(Com::tSlash);
|
|
}
|
|
#if JSON_OUTPUT
|
|
if (isJson) {
|
|
if (!firstFile) Com::print(',');
|
|
Com::print('"');Com::print('*');
|
|
SDCard::printEscapeChars(tempLongFilename);
|
|
Com::print('"');
|
|
firstFile = false;
|
|
} else {
|
|
Com::print(tempLongFilename);
|
|
Com::printFLN(Com::tSlash); // End with / to mark it as directory entry, so we can see empty directories.
|
|
}
|
|
#else
|
|
Com::print(tempLongFilename);
|
|
Com::printFLN(Com::tSlash); // End with / to mark it as directory entry, so we can see empty directories.
|
|
#endif
|
|
}
|
|
SdBaseFile next;
|
|
char *tmp;
|
|
|
|
if(level) strcat(fullName, "/");
|
|
|
|
strcat(fullName, tempLongFilename);
|
|
uint16_t index = (parent->curPosition()-31) >> 5;
|
|
|
|
if(!isJson && next.open(parent, index, O_READ))
|
|
{
|
|
if (next.lsRecursive(&next,level+1, findFilename, pParentFound,false))
|
|
return true;
|
|
}
|
|
parent->seekSet(32 * (index + 1));
|
|
if ((tmp = strrchr(fullName, '/'))!= NULL)
|
|
*tmp = 0;
|
|
else
|
|
*fullName = 0;
|
|
}
|
|
else
|
|
{
|
|
if (findFilename != NULL)
|
|
{
|
|
int8_t cFullname;
|
|
|
|
cFullname = strlen(fullName);
|
|
if (RFstrnicmp(fullName, findFilename, cFullname) == 0)
|
|
{
|
|
if (cFullname > 0)
|
|
cFullname++;
|
|
if (RFstricmp(tempLongFilename, findFilename+cFullname) == 0)
|
|
{
|
|
if (pParentFound != NULL)
|
|
*pParentFound = *parent;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(level && !isJson)
|
|
{
|
|
Com::print(fullName);
|
|
Com::printF(Com::tSlash);
|
|
}
|
|
#if JSON_OUTPUT
|
|
if (isJson) {
|
|
if (!firstFile) Com::printF(Com::tComma);
|
|
Com::print('"');
|
|
SDCard::printEscapeChars(tempLongFilename);
|
|
Com::print('"');
|
|
firstFile = false;
|
|
} else
|
|
#endif
|
|
{
|
|
Com::print(tempLongFilename);
|
|
#if SD_EXTENDED_DIR
|
|
Com::printF(Com::tSpace, (long) p->fileSize);
|
|
#endif
|
|
Com::println();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/** 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(uint8_t flags, uint8_t indent) {
|
|
SdBaseFile parent;
|
|
rewind();
|
|
*fullName = 0;
|
|
pathend = fullName;
|
|
parent = *this;
|
|
lsRecursive(&parent, 0, NULL, NULL, false);
|
|
}
|
|
|
|
#if JSON_OUTPUT
|
|
void SdBaseFile::lsJSON() {
|
|
SdBaseFile parent;
|
|
rewind();
|
|
*fullName = 0;
|
|
parent = *this;
|
|
lsRecursive(&parent, 0, NULL, NULL, true);
|
|
}
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// saves 32 bytes on stack for ls recursion
|
|
// return 0 - EOF, 1 - normal file, or 2 - directory
|
|
int8_t SdBaseFile::lsPrintNext(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++) Com::print(' ');
|
|
|
|
printDirName(dir, flags & (LS_DATE | LS_SIZE) ? 14 : 0, true);
|
|
|
|
// print modify date/time if requested
|
|
if (flags & LS_DATE) {
|
|
Com::print(' ');
|
|
printFatDate(dir.lastWriteDate);
|
|
Com::print(' ');
|
|
printFatTime(dir.lastWriteTime);
|
|
}
|
|
// print size if requested
|
|
if (!DIR_IS_SUBDIR(&dir) && (flags & LS_SIZE)) {
|
|
Com::print(' ');
|
|
Com::print(dir.fileSize);
|
|
}
|
|
Com::println();
|
|
return DIR_IS_FILE(&dir) ? 1 : 2;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
// format directory name field from a 8.3 name string
|
|
FSTRINGVALUE(illegalFileChars,"|<>^+=?/[];,*\"\\")
|
|
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) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail; // only one dot allowed
|
|
}
|
|
n = 10; // max index for full 8.3 name
|
|
i = 8; // place for extension
|
|
} else {
|
|
// illegal FAT characters
|
|
#define FLASH_ILLEGAL_CHARS
|
|
#ifdef FLASH_ILLEGAL_CHARS
|
|
// store chars in flash
|
|
FSTRINGPARAM(p);
|
|
p = illegalFileChars;
|
|
uint8_t b;
|
|
while ((b = HAL::readFlashByte(p++))) {
|
|
if (b == c) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
#else // FLASH_ILLEGAL_CHARS
|
|
// store chars in RAM
|
|
if (strchr("|<>^+=?/[];,*\"\\", c)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
#endif // FLASH_ILLEGAL_CHARS
|
|
|
|
// check size and only allow ASCII printable characters
|
|
if (i > n || c < 0X20 || c > 0X7E) {
|
|
c = '_';
|
|
}
|
|
// 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[LONG_FILENAME_LENGTH+1];
|
|
SdBaseFile newParent;
|
|
|
|
if (openParentReturnFile(parent, path, dname, &newParent, pFlag))
|
|
{
|
|
return mkdir(&newParent, dname);
|
|
}
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool SdBaseFile::mkdir(SdBaseFile* parent, const uint8_t *dname) {
|
|
dir_t d;
|
|
|
|
if (!parent->isDir()) {
|
|
DBG_FAIL_MACRO;
|
|
return false;
|
|
}
|
|
|
|
// create a normal file
|
|
if (!open(parent, dname, O_CREAT | O_EXCL | O_RDWR, true)) {
|
|
DBG_FAIL_MACRO;
|
|
return false;
|
|
}
|
|
|
|
// make entry for '.'
|
|
memset(&d, 0, sizeof(d));
|
|
d.creationDate = FAT_DEFAULT_DATE;
|
|
d.creationTime = FAT_DEFAULT_TIME;
|
|
d.lastAccessDate = d.creationDate;
|
|
d.lastWriteDate = d.creationDate;
|
|
d.lastWriteTime = d.creationTime;
|
|
|
|
d.name[0] = '.';
|
|
d.attributes = DIR_ATT_DIRECTORY;
|
|
for (uint8_t i = 1; i < 11; i++) d.name[i] = ' ';
|
|
|
|
if (write(&d, sizeof(dir_t)) < 0)
|
|
return false;
|
|
sync();
|
|
|
|
// 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;
|
|
}
|
|
if (write(&d, sizeof(dir_t)) < 0)
|
|
return false;
|
|
sync();
|
|
memset(&d, 0, sizeof(dir_t));
|
|
if (write(&d, sizeof(dir_t)) < 0)
|
|
return false;
|
|
sync();
|
|
// fileSize_ = 0;
|
|
type_ = FAT_FILE_TYPE_SUBDIR;
|
|
flags_ |= F_FILE_DIR_DIRTY;
|
|
return true;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** 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), writelnmkdir_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::openParentReturnFile(SdBaseFile* dirFile, const char* path, uint8_t *dname,
|
|
SdBaseFile *newParent, boolean bMakeDirs) {
|
|
SdBaseFile dir1, dir2;
|
|
SdBaseFile *parent = dirFile;
|
|
//dir_t *pEntry;
|
|
SdBaseFile *sub = &dir1;
|
|
char *p;
|
|
//boolean bFound;
|
|
|
|
#ifdef GLENN_DEBUG
|
|
Commands::checkFreeMemory();
|
|
Commands::writeLowestFreeRAM();
|
|
#endif
|
|
|
|
*dname = 0;
|
|
|
|
if (!dirFile) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// error if already open
|
|
if (isOpen()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (*path == '/') {
|
|
while (*path == '/') path++;
|
|
if (!dirFile->isRoot())
|
|
{
|
|
if (!dir2.openRoot(dirFile->vol_))
|
|
{
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
parent = &dir2;
|
|
}
|
|
}
|
|
// Traverse the Long Directory Name Path until we get to the LEAF (long file name)
|
|
while ((p = strchr(path, '/')) != NULL)
|
|
{
|
|
int8_t cb = p-path;
|
|
|
|
memcpy(dname, path, cb);
|
|
*(dname+cb) = 0;
|
|
|
|
if (*(p+1) == 0)
|
|
goto success;
|
|
#ifdef GLENN_DEBUG
|
|
Commands::checkFreeMemory();
|
|
Commands::writeLowestFreeRAM();
|
|
#endif
|
|
//bFound = false;
|
|
if (!sub->open(parent, dname, O_READ, false))
|
|
{
|
|
if (!bMakeDirs)
|
|
return false;
|
|
if (!sub->mkdir(parent, dname))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
if (parent != dirFile) parent->close();
|
|
parent = sub;
|
|
sub = parent != &dir1 ? &dir1 : &dir2;
|
|
path = p+1;
|
|
}
|
|
strcpy((char *)dname, path);
|
|
|
|
success:
|
|
*newParent = *parent;
|
|
#ifdef GLENN_DEBUG
|
|
Commands::checkFreeMemory();
|
|
Commands::writeLowestFreeRAM();
|
|
#endif
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
|
|
bool SdBaseFile::open(SdBaseFile* dirFile, const char* path, uint8_t oflag)
|
|
{
|
|
uint8_t dname[LONG_FILENAME_LENGTH+1];
|
|
SdBaseFile parent;
|
|
|
|
if (openParentReturnFile(dirFile, path, dname, &parent, false))
|
|
{
|
|
if (*dname == 0)
|
|
return true;
|
|
return open(&parent, dname, oflag, false);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
uint8_t SdBaseFile::lfn_checksum(const unsigned char *pFCBName)
|
|
{
|
|
int i;
|
|
unsigned char sum = 0;
|
|
|
|
for (i = 11; i; i--)
|
|
sum = ((sum & 1) << 7) + (sum >> 1) + *pFCBName++;
|
|
|
|
return sum;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
// open with filename in dname
|
|
bool SdBaseFile::open(SdBaseFile* dirFile,const uint8_t *dname, uint8_t oflag, bool bDir) {
|
|
bool emptyFound = false;
|
|
uint8_t index = 0;
|
|
dir_t tempDir, *p = NULL;
|
|
const char *tempPtr;
|
|
char newName[SHORT_FILENAME_LENGTH+2];
|
|
boolean bShortName = false;
|
|
int8_t cVFATNeeded = -1, cVFATFoundCur;
|
|
uint32_t wIndexPos = 0;
|
|
uint8_t cbFilename;
|
|
char *Filename = (char *)dname;
|
|
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("Open File:");
|
|
Com::print((char*)dname);
|
|
Com::println();
|
|
#endif
|
|
vol_ = dirFile->vol_;
|
|
dirFile->rewind();
|
|
// search for file
|
|
|
|
if (oflag & O_CREAT)
|
|
{
|
|
int8_t cb = strlen((char *)dname);
|
|
bShortName = cb < 9;
|
|
cVFATNeeded = (cb / 13) + (cb % 13 == 0 ? 0 : 1);
|
|
}
|
|
|
|
while ((p = dirFile->getLongFilename(p, tempLongFilename, cVFATNeeded, &wIndexPos)))
|
|
{
|
|
HAL::pingWatchdog();
|
|
index = (0XF & ((dirFile->curPosition_-31) >> 5));
|
|
if (RFstricmp(tempLongFilename, (char *)dname) == 0)
|
|
{
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("FFound");
|
|
Com::println();
|
|
#endif
|
|
if (oflag & O_EXCL)
|
|
{
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
return openCachedEntry(index, oflag);
|
|
}
|
|
}
|
|
|
|
// don't create unless O_CREAT and O_WRITE
|
|
if (!(oflag & O_CREAT) || !(oflag & O_WRITE)) {
|
|
goto fail;
|
|
}
|
|
|
|
dirFile->findSpace(&tempDir, cVFATNeeded, &cVFATFoundCur, &wIndexPos);
|
|
if (wIndexPos != 0)
|
|
{
|
|
emptyFound = true;
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("Empty FAT:");
|
|
Com::print((long)wIndexPos);
|
|
Com::println();
|
|
#endif
|
|
index = wIndexPos >> 5;
|
|
}
|
|
else
|
|
{
|
|
// only 512 entries allowed in FAT16 Root Fixed dir
|
|
if (dirFile->type() == FAT_FILE_TYPE_ROOT_FIXED && (dirFile->curPosition_ >> 5) >= 512)
|
|
goto fail;
|
|
cVFATFoundCur = cVFATNeeded + 1;
|
|
if (dirFile->curPosition_ > 0)
|
|
wIndexPos = dirFile->curPosition_-32;
|
|
}
|
|
p = &tempDir;
|
|
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("CurPos:");
|
|
Com::print((long)wIndexPos);
|
|
Com::println();
|
|
#endif
|
|
dirFile->flags_ |= O_WRITE;
|
|
dirFile->seekSet(wIndexPos);
|
|
|
|
// Create LONG FILE NAMES and LONG DIRECTORIES HERE
|
|
// FILL IN MULTIPLE dir_t enteries..
|
|
// DO a test and and make all files created have a long file name of "XXXXXXX <shortname>"
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("At Index:");
|
|
Com::print((long)index);
|
|
Com::print("-");
|
|
Com::print((long)bShortName);
|
|
Com::println();
|
|
|
|
Com::print("Create:");
|
|
Com::print((char *)dname);
|
|
Com::println();
|
|
#endif
|
|
if (!bShortName)
|
|
{
|
|
char *pExt, szExt[5];
|
|
|
|
// Generate 8.3 from longfile name
|
|
if ((pExt = strchr((char *)dname, '.')) != NULL)
|
|
{
|
|
strncpy(szExt, pExt, 4);
|
|
szExt[4] = 0;
|
|
if (pExt > (char*)dname+6)
|
|
pExt = (char*)dname+6;
|
|
}
|
|
else
|
|
{
|
|
szExt[0] = 0;
|
|
pExt = (char*)dname+6;
|
|
}
|
|
uint8_t cb = pExt-(char *)dname;
|
|
memcpy(newName, dname, cb);
|
|
newName[cb] = 0;
|
|
strcat(newName, "~1");
|
|
strcat(newName, szExt);
|
|
}
|
|
else
|
|
{
|
|
strcpy(newName, (char *)dname);
|
|
}
|
|
|
|
uint8_t checksum;
|
|
|
|
make83Name(newName, (uint8_t *)p->name, &tempPtr);
|
|
checksum = lfn_checksum(p->name);
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("Name:");
|
|
Com::print((char *)p->name);
|
|
Com::println();
|
|
#endif
|
|
cbFilename = strlen(Filename);
|
|
|
|
// Write Long File Name VFAT entries to file
|
|
for(uint8_t iBlk=cVFATNeeded;iBlk>0;iBlk--)
|
|
{
|
|
vfat_t *VFAT = (vfat_t *)p;
|
|
uint8_t n;
|
|
|
|
n = (iBlk-1) * 13;
|
|
memset(p, 0, sizeof(dir_t));
|
|
p->attributes = DIR_ATT_LONG_NAME;
|
|
VFAT->sequenceNumber = iBlk | (iBlk == cVFATNeeded ? 0x40 : 0);
|
|
|
|
uint16_t *pName = VFAT->name1;
|
|
|
|
for(int8_t i=0;i<13;i++)
|
|
{
|
|
if (n+i > cbFilename)
|
|
*pName++ = 0xffff;
|
|
else
|
|
*pName++ = (uint16_t)Filename[n+i];
|
|
if (i == 4)
|
|
pName = VFAT->name2;
|
|
else if (i == 10)
|
|
pName = VFAT->name3;
|
|
}
|
|
VFAT->checksum = checksum;
|
|
if (dirFile->write(p, sizeof(dir_t)) < 0)
|
|
goto fail;
|
|
dirFile->sync();
|
|
}
|
|
// END WRITING LONG FILE NAME BLK
|
|
|
|
// Start 8.3 file init
|
|
// initialize as empty file
|
|
memset(p, 0, sizeof(dir_t));
|
|
|
|
make83Name(newName, (uint8_t *)p->name, &tempPtr);
|
|
|
|
p->attributes = bDir ? DIR_ATT_DIRECTORY : DIR_ATT_ARCHIVE;
|
|
|
|
p->creationDate = FAT_DEFAULT_DATE;
|
|
p->creationTime = FAT_DEFAULT_TIME;
|
|
p->lastAccessDate = p->creationDate;
|
|
p->lastWriteDate = p->creationDate;
|
|
p->lastWriteTime = p->creationTime;
|
|
|
|
if (dirFile->write(p, sizeof(dir_t)) < 0)
|
|
goto fail;
|
|
dirFile->sync();
|
|
|
|
memset(p, 0, sizeof(dir_t));
|
|
|
|
if (emptyFound)
|
|
p->name[0] = DIR_NAME_DELETED;
|
|
|
|
for(int8_t i=0;i< cVFATFoundCur - cVFATNeeded;i++)
|
|
{
|
|
if (dirFile->write(p, sizeof(dir_t)) < 0)
|
|
goto fail;
|
|
dirFile->sync();
|
|
}
|
|
return open(dirFile, (wIndexPos >> 5) + (cVFATNeeded), oflag & ~O_EXCL);
|
|
|
|
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) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
|
|
// don't open existing file if O_EXCL - user call error
|
|
if (oflag & O_EXCL) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// seek to location of entry
|
|
if (!dirFile->seekSet(32 * index)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// read entry into cache
|
|
p = dirFile->readDirCache();
|
|
if (!p) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// error if empty slot or '.' or '..'
|
|
if (p->name[0] == DIR_NAME_FREE ||
|
|
p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') {
|
|
DBG_FAIL_MACRO;
|
|
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_->cacheAddress()->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)) {
|
|
// DBG_FAIL_MACRO;
|
|
// 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;
|
|
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("CATR:");
|
|
Com::print(firstCluster_);
|
|
Com::print("-");
|
|
Com::print(p->name[0]);
|
|
Com::println();
|
|
#endif
|
|
|
|
// 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 (!setDirSize()) {
|
|
fileSize_= 0;
|
|
}
|
|
type_ = FAT_FILE_TYPE_SUBDIR;
|
|
} else {
|
|
DBG_FAIL_MACRO;
|
|
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)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
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) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// error if already open
|
|
if (isOpen()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
vol_ = dirFile->vol_;
|
|
|
|
while (1) {
|
|
index = 0XF & (dirFile->curPosition_ >> 5);
|
|
|
|
// read entry into cache
|
|
p = dirFile->readDirCache();
|
|
if (!p) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// done if last entry
|
|
if (p->name[0] == DIR_NAME_FREE) {
|
|
DBG_FAIL_MACRO;
|
|
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;
|
|
cache_t* pc;
|
|
// error if already open or dir is root or dir is not a directory
|
|
if (isOpen() || !dir || dir->isRoot() || !dir->isDir()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
vol_ = dir->vol_;
|
|
// position to '..'
|
|
if (!dir->seekSet(32)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// read '..' entry
|
|
if (dir->read(&entry, sizeof(entry)) != 32) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// verify it is '..'
|
|
if (entry.name[0] != '.' || entry.name[1] != '.') {
|
|
DBG_FAIL_MACRO;
|
|
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
|
|
pc = vol_->cacheFetch(lbn, SdVolume::CACHE_FOR_READ);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
p = &pc->dir[1];
|
|
// verify name for '../..'
|
|
if (p->name[0] != '.' || p->name[1] != '.') {
|
|
DBG_FAIL_MACRO;
|
|
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())) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
} else {
|
|
if (!file.openCachedEntry(1, O_READ)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
// search for parent in '../..'
|
|
do {
|
|
if (file.readDir(&entry, NULL) != 32) {
|
|
DBG_FAIL_MACRO;
|
|
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()) {
|
|
#if defined(DEBUG_SD_ERROR)
|
|
Com::printErrorFLN(PSTR("Root already open"));
|
|
#endif
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
vol_ = vol;
|
|
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 (!setDirSize()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
} else {
|
|
// volume is not initialized, invalid, or FAT12 without support
|
|
#if defined(DEBUG_SD_ERROR)
|
|
Com::printErrorF(PSTR("volume is not initialized, invalid, or FAT12 without support, type:"));
|
|
Com::print((int)vol->fatType());Com::println();
|
|
#endif
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// 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:
|
|
#if defined(DEBUG_SD_ERROR)
|
|
Com::printErrorFLN(PSTR("SD open root dir failed"));
|
|
#endif
|
|
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() {
|
|
FatPos_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.
|
|
* \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(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) {
|
|
Com::print('.');
|
|
w++;
|
|
}
|
|
Com::print(dir.name[i]);
|
|
w++;
|
|
}
|
|
if (DIR_IS_SUBDIR(&dir) && printSlash) {
|
|
Com::print('/');
|
|
w++;
|
|
}
|
|
while (w < width) {
|
|
Com::print(' ');
|
|
w++;
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
// print uint8_t with width 2
|
|
static void print2u(uint8_t v) {
|
|
if (v < 10) Com::print('0');
|
|
Com::print(v);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Print a file's creation date and time
|
|
*
|
|
* \param[in] pr Print stream for output.
|
|
*
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool SdBaseFile::printCreateDateTime() {
|
|
dir_t dir;
|
|
if (!dirEntry(&dir)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
printFatDate(dir.creationDate);
|
|
Com::print(' ');
|
|
printFatTime(dir.creationTime);
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/** %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(uint16_t fatDate) {
|
|
Com::print((int)FAT_YEAR(fatDate));
|
|
Com::print('-');
|
|
print2u(FAT_MONTH(fatDate));
|
|
Com::print('-');
|
|
print2u(FAT_DAY(fatDate));
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/** %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( uint16_t fatTime) {
|
|
print2u(FAT_HOUR(fatTime));
|
|
Com::print(':');
|
|
print2u(FAT_MINUTE(fatTime));
|
|
Com::print(':');
|
|
print2u(FAT_SECOND(fatTime));
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Print a file's modify date and time
|
|
*
|
|
* \param[in] pr Print stream for output.
|
|
*
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool SdBaseFile::printModifyDateTime() {
|
|
dir_t dir;
|
|
if (!dirEntry(&dir)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
printFatDate(dir.lastWriteDate);
|
|
Com::print(' ');
|
|
printFatTime(dir.lastWriteTime);
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
/** Template for SdBaseFile::printField() */
|
|
|
|
template <typename Type>
|
|
|
|
static int printFieldT(SdBaseFile* file, char sign, Type value, char term) {
|
|
char buf[3*sizeof(Type) + 3];
|
|
char* str = &buf[sizeof(buf)];
|
|
|
|
if (term) {
|
|
*--str = term;
|
|
if (term == '\n') {
|
|
*--str = '\r';
|
|
}
|
|
}
|
|
do {
|
|
Type m = value;
|
|
value /= 10;
|
|
*--str = '0' + m - 10*value;
|
|
} while (value);
|
|
if (sign) {
|
|
*--str = sign;
|
|
}
|
|
return file->write(str, &buf[sizeof(buf)] - str);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Print a number followed by a field terminator.
|
|
* \param[in] value The number to be printed.
|
|
* \param[in] term The field terminator. Use '\\n' for CR LF.
|
|
* \return The number of bytes written or -1 if an error occurs.
|
|
*/
|
|
int SdBaseFile::printField(uint16_t value, char term) {
|
|
return printFieldT(this, 0, value, term);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Print a number followed by a field terminator.
|
|
* \param[in] value The number to be printed.
|
|
* \param[in] term The field terminator. Use '\\n' for CR LF.
|
|
* \return The number of bytes written or -1 if an error occurs.
|
|
*/
|
|
int SdBaseFile::printField(int16_t value, char term) {
|
|
char sign = 0;
|
|
if (value < 0) {
|
|
sign = '-';
|
|
value = -value;
|
|
}
|
|
return printFieldT(this, sign, (uint16_t)value, term);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Print a number followed by a field terminator.
|
|
* \param[in] value The number to be printed.
|
|
* \param[in] term The field terminator. Use '\\n' for CR LF.
|
|
* \return The number of bytes written or -1 if an error occurs.
|
|
*/
|
|
int SdBaseFile::printField(uint32_t value, char term) {
|
|
return printFieldT(this, 0, value, term);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Print a number followed by a field terminator.
|
|
* \param[in] value The number to be printed.
|
|
* \param[in] term The field terminator. Use '\\n' for CR LF.
|
|
* \return The number of bytes written or -1 if an error occurs.
|
|
*/
|
|
int SdBaseFile::printField(int32_t value, char term) {
|
|
char sign = 0;
|
|
if (value < 0) {
|
|
sign = '-';
|
|
value = -value;
|
|
}
|
|
return printFieldT(this, sign, (uint32_t)value, term);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//------------------------------------------------------------------------------
|
|
/** Print a file's name
|
|
*
|
|
* \param[in] pr Print stream for output.
|
|
*
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool SdBaseFile::printName() {
|
|
char name[13];
|
|
if (!getFilename(name)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
Com::print(name);
|
|
return true;
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** 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.
|
|
*/
|
|
int SdBaseFile::read(void* buf, size_t nbyte) {
|
|
uint8_t blockOfCluster;
|
|
uint8_t* dst = reinterpret_cast<uint8_t*>(buf);
|
|
uint16_t offset;
|
|
size_t toRead;
|
|
uint32_t block; // raw device block number
|
|
cache_t* pc;
|
|
|
|
// error if not open or write only
|
|
if (!isOpen() || !(flags_ & O_READ)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// max bytes left in file
|
|
if (nbyte >= (fileSize_ - curPosition_)) {
|
|
nbyte = fileSize_ - curPosition_;
|
|
}
|
|
// amount left to read
|
|
toRead = nbyte;
|
|
while (toRead > 0) {
|
|
size_t n;
|
|
offset = curPosition_ & 0X1FF; // offset in block
|
|
blockOfCluster = vol_->blockOfCluster(curPosition_);
|
|
if (type_ == FAT_FILE_TYPE_ROOT_FIXED) {
|
|
block = vol_->rootDirStart() + (curPosition_ >> 9);
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("RBL:");
|
|
Com::print(block);
|
|
Com::println();
|
|
#endif
|
|
} else {
|
|
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_)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
block = vol_->clusterStartBlock(curCluster_) + blockOfCluster;
|
|
}
|
|
if (offset != 0 || toRead < 512 || block == vol_->cacheBlockNumber()) {
|
|
// amount to be read from current block
|
|
n = 512 - offset;
|
|
if (n > toRead) n = toRead;
|
|
// read block to cache and copy data to caller
|
|
pc = vol_->cacheFetch(block, SdVolume::CACHE_FOR_READ);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
uint8_t* src = pc->data + offset;
|
|
memcpy(dst, src, n);
|
|
} else if (!USE_MULTI_BLOCK_SD_IO || toRead < 1024) {
|
|
// read single block
|
|
n = 512;
|
|
if (!vol_->readBlock(block, dst)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
} else {
|
|
uint8_t nb = toRead >> 9;
|
|
if (type_ != FAT_FILE_TYPE_ROOT_FIXED) {
|
|
uint8_t mb = vol_->blocksPerCluster() - blockOfCluster;
|
|
if (mb < nb) nb = mb;
|
|
}
|
|
n = 512*nb;
|
|
if (vol_->cacheBlockNumber() <= block
|
|
&& block < (vol_->cacheBlockNumber() + nb)) {
|
|
// flush cache if a block is in the cache
|
|
if (!vol_->cacheSync()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
if (!vol_->sdCard()->readStart(block)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
for (uint8_t b = 0; b < nb; b++) {
|
|
if (!vol_->sdCard()->readData(dst + b*512)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
if (!vol_->sdCard()->readStop()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
dst += n;
|
|
curPosition_ += n;
|
|
toRead -= n;
|
|
}
|
|
return nbyte;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
/** Read the next directory entry from a directory file with the long filename
|
|
*
|
|
* \param[out] dir The dir_t struct that will receive the data.
|
|
* \param[out] longFiename The long filename associated with the 8.3 name
|
|
*
|
|
* \return For success getLongFilename() returns a pointer to dir_t
|
|
* A value of zero will be returned if end of file is reached.
|
|
*/
|
|
|
|
|
|
dir_t *SdBaseFile::getLongFilename(dir_t *dir, char *longFilename, int8_t cVFATNeeded, uint32_t *pwIndexPos)
|
|
{
|
|
int16_t n;
|
|
uint8_t bLastPart = true;
|
|
uint8_t checksum;
|
|
|
|
if (longFilename != NULL)
|
|
*longFilename = 0;
|
|
|
|
while (1)
|
|
{
|
|
HAL::pingWatchdog();
|
|
#ifdef GLENN_DEBUG
|
|
Commands::checkFreeMemory();
|
|
Commands::writeLowestFreeRAM();
|
|
#endif
|
|
if (!(dir = readDirCache()))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (dir->name[0] == DIR_NAME_FREE)
|
|
return NULL;
|
|
|
|
if (dir->name[0] == DIR_NAME_0XE5 || dir->name[0] == DIR_NAME_DELETED)
|
|
{
|
|
bLastPart = true;
|
|
if (longFilename != NULL)
|
|
*longFilename = 0;
|
|
continue;
|
|
}
|
|
|
|
if (DIR_IS_LONG_NAME(dir))
|
|
{
|
|
if (longFilename != NULL)
|
|
{
|
|
vfat_t *VFAT = (vfat_t*)dir;
|
|
int8_t nSeq = VFAT->sequenceNumber & 0x1F;
|
|
|
|
// Sanity check the VFAT entry. The first cluster is always set to zero. And the sequence number should be higher then 0
|
|
if (VFAT->firstClusterLow == 0 && nSeq > 0 && nSeq <= MAX_VFAT_ENTRIES)
|
|
{
|
|
n = (nSeq - 1) * 13;
|
|
|
|
longFilename[n+0] = (char)VFAT->name1[0];
|
|
|
|
longFilename[n+1] = (char)VFAT->name1[1];
|
|
longFilename[n+2] = (char)VFAT->name1[2];
|
|
longFilename[n+3] = (char)VFAT->name1[3];
|
|
longFilename[n+4] = (char)VFAT->name1[4];
|
|
longFilename[n+5] = (char)VFAT->name2[0];
|
|
longFilename[n+6] = (char)VFAT->name2[1];
|
|
longFilename[n+7] = (char)VFAT->name2[2];
|
|
longFilename[n+8] = (char)VFAT->name2[3];
|
|
longFilename[n+9] = (char)VFAT->name2[4];
|
|
longFilename[n+10] = (char)VFAT->name2[5];
|
|
longFilename[n+11] = (char)VFAT->name3[0];
|
|
longFilename[n+12] = (char)VFAT->name3[1];
|
|
|
|
if (bLastPart)
|
|
{
|
|
checksum = VFAT->checksum;
|
|
longFilename[n+13] = 0;
|
|
}
|
|
bLastPart = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((dir->attributes & DIR_ATT_HIDDEN || dir->attributes & DIR_ATT_SYSTEM) || (dir->name[0] == '.' && dir->name[1] != '.'))
|
|
{
|
|
bLastPart = true;
|
|
if (longFilename != NULL)
|
|
*longFilename = 0;
|
|
continue;
|
|
}
|
|
if (DIR_IS_FILE(dir) || DIR_IS_SUBDIR(dir))
|
|
{
|
|
if (longFilename && (bLastPart || checksum != lfn_checksum(dir->name)))
|
|
{
|
|
sd.createFilename(longFilename, *dir);
|
|
}
|
|
return dir;
|
|
}
|
|
}
|
|
}
|
|
#ifdef GLENN_DEBUG
|
|
Commands::checkFreeMemory();
|
|
Commands::writeLowestFreeRAM();
|
|
#endif
|
|
return dir;
|
|
}
|
|
|
|
bool SdBaseFile::findSpace(dir_t *dir, int8_t cVFATNeeded, int8_t *pcVFATFound, uint32_t *pwIndexPos)
|
|
{
|
|
//int16_t n; // unused
|
|
int8_t cVFATFound = 0;
|
|
// if not a directory file or miss-positioned return an error
|
|
if (!isDir()) return -1;
|
|
|
|
rewind();
|
|
|
|
while (1) {
|
|
HAL::pingWatchdog();
|
|
dir = readDirCache();
|
|
if (!dir) return false;
|
|
// last entry if DIR_NAME_FREE
|
|
if (dir->name[0] == DIR_NAME_FREE) return 0;
|
|
// skip empty entries and entry for . and ..
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("Attr:");
|
|
Com::print((long)dir->attributes);
|
|
Com::print(" ");
|
|
Com::print((long)dir->name[0]);
|
|
Com::println();
|
|
#endif
|
|
|
|
if (dir->name[0] == DIR_NAME_0XE5 || dir->name[0] == DIR_NAME_DELETED)
|
|
{
|
|
if (DIR_IS_LONG_NAME(dir))
|
|
{
|
|
//vfat_t *VFAT = (vfat_t*)dir; // unused
|
|
cVFATFound++;
|
|
}
|
|
else
|
|
{
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("Need: ");
|
|
Com::print(cVFATNeeded);
|
|
Com::print(" Got: ");
|
|
Com::print(cVFATFound);
|
|
Com::print(" ");
|
|
Com::println();
|
|
#endif
|
|
if (pwIndexPos != NULL && cVFATNeeded > 0 && cVFATFound >= cVFATNeeded && *pwIndexPos == 0)
|
|
{
|
|
*pwIndexPos = curPosition_-sizeof(dir_t)-(cVFATFound * sizeof(dir_t));
|
|
*pcVFATFound = cVFATFound;
|
|
return true;
|
|
}
|
|
cVFATFound++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cVFATFound = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
/** 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, char* longFilename) {
|
|
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()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// index of entry in cache
|
|
i = (curPosition_ >> 5) & 0XF;
|
|
// use read to locate and cache block
|
|
if (read() < 0) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// advance to next entry
|
|
curPosition_ += 31;
|
|
|
|
// return pointer to entry
|
|
return vol_->cacheAddress()->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)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// cache directory entry
|
|
d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
|
if (!d) {
|
|
DBG_FAIL_MACRO;
|
|
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_->cacheSync();
|
|
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)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
return file.remove();
|
|
|
|
fail:
|
|
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;
|
|
cache_t* pc;
|
|
dir_t* d;
|
|
|
|
// must be an open file or subdirectory
|
|
if (!(isFile() || isSubDir())) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// can't move file
|
|
if (vol_ != dirFile->vol_) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// sync() and cache directory entry
|
|
sync();
|
|
d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
|
if (!d) {
|
|
DBG_FAIL_MACRO;
|
|
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) {
|
|
DBG_FAIL_MACRO;
|
|
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);
|
|
pc = vol_->cacheFetch(block, SdVolume::CACHE_FOR_READ);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
memcpy(&entry, &pc->dir[1], sizeof(entry));
|
|
|
|
// free unused cluster
|
|
if (!vol_->freeChain(dirCluster)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// store new dot dot
|
|
block = vol_->clusterStartBlock(firstCluster_);
|
|
pc = vol_->cacheFetch(block, SdVolume::CACHE_FOR_WRITE);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
memcpy(&pc->dir[1], &entry, sizeof(entry));
|
|
}
|
|
return vol_->cacheSync();
|
|
|
|
restore:
|
|
d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
|
if (!d) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// restore entry
|
|
d->name[0] = entry.name[0];
|
|
vol_->cacheSync();
|
|
|
|
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()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
rewind();
|
|
|
|
// make sure directory is empty
|
|
while (curPosition_ < fileSize_) {
|
|
dir_t* p = readDirCache();
|
|
if (!p) {
|
|
DBG_FAIL_MACRO;
|
|
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)) {
|
|
DBG_FAIL_MACRO;
|
|
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) {
|
|
DBG_FAIL_MACRO;
|
|
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)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (f.isSubDir()) {
|
|
// recursively delete
|
|
if (!f.rmRfStar()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
} else {
|
|
// ignore read-only
|
|
f.flags_ |= O_WRITE;
|
|
if (!f.remove()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
// position to next entry if required
|
|
if (curPosition_ != (32UL*(index + 1))) {
|
|
if (!seekSet(32UL*(index + 1))) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
// don't try to delete root
|
|
if (!isRoot()) {
|
|
if (!rmdir()) {
|
|
DBG_FAIL_MACRO;
|
|
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_) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (type_ == FAT_FILE_TYPE_ROOT_FIXED) {
|
|
curPosition_ = pos;
|
|
curCluster_ = 0;
|
|
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_)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
curPosition_ = pos;
|
|
|
|
done:
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
// set fileSize_ for a directory
|
|
bool SdBaseFile::setDirSize() {
|
|
uint16_t s = 0;
|
|
uint32_t cluster = firstCluster_;
|
|
do {
|
|
if (!vol_->fatGet(cluster, &cluster)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
s += vol_->blocksPerCluster();
|
|
// max size if a directory file is 4096 blocks
|
|
if (s >= 4096) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
} while (!vol_->isEOC(cluster));
|
|
fileSize_ = 512L*s;
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//------------------------------------------------------------------------------
|
|
void SdBaseFile::setpos(FatPos_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()) {
|
|
DBG_FAIL_MACRO;
|
|
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) {
|
|
DBG_FAIL_MACRO;
|
|
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_->cacheSync();
|
|
|
|
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)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// update directory fields
|
|
if (!sync()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
|
if (!d) {
|
|
DBG_FAIL_MACRO;
|
|
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_->cacheSync();
|
|
|
|
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) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// update directory entry
|
|
if (!sync()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
|
|
if (!d) {
|
|
DBG_FAIL_MACRO;
|
|
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_->cacheSync();
|
|
|
|
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)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// error if length is greater than current size
|
|
if (length > fileSize_) {
|
|
DBG_FAIL_MACRO;
|
|
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)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (length == 0) {
|
|
// free all clusters
|
|
if (!vol_->freeChain(firstCluster_)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
firstCluster_ = 0;
|
|
} else {
|
|
uint32_t toFree;
|
|
if (!vol_->fatGet(curCluster_, &toFree)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (!vol_->isEOC(toFree)) {
|
|
// free extra clusters
|
|
if (!vol_->freeChain(toFree)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// current cluster is end of chain
|
|
if (!vol_->fatPutEOC(curCluster_)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
fileSize_ = length;
|
|
|
|
// need to update directory entry
|
|
flags_ |= F_FILE_DIR_DIRTY;
|
|
|
|
if (!sync()) {
|
|
DBG_FAIL_MACRO;
|
|
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.
|
|
*
|
|
*/
|
|
int SdBaseFile::write(const void* buf, size_t nbyte) {
|
|
// convert void* to uint8_t* - must be before goto statements
|
|
const uint8_t* src = reinterpret_cast<const uint8_t*>(buf);
|
|
cache_t* pc;
|
|
uint8_t cacheOption;
|
|
// number of bytes left to write - must be before goto statements
|
|
size_t nToWrite = nbyte;
|
|
size_t n;
|
|
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("Cur Pos:");
|
|
Com::print(curPosition_);
|
|
Com::println();
|
|
#endif
|
|
|
|
// error if not a normal file or is read-only
|
|
if (/*!isFile() || */!(flags_ & O_WRITE)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
|
|
// seek to end of file if append flag
|
|
if ((flags_ & O_APPEND) && curPosition_ != fileSize_) {
|
|
if (!seekEnd()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
while (nToWrite) {
|
|
uint8_t blockOfCluster = vol_->blockOfCluster(curPosition_);
|
|
uint16_t blockOffset = curPosition_ & 0X1FF;
|
|
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("BC");
|
|
Com::print((long)blockOfCluster);
|
|
Com::print("-");
|
|
Com::print((long)blockOffset);
|
|
Com::print("-");
|
|
Com::print((long)curCluster_);
|
|
Com::print("-");
|
|
Com::print((long)firstCluster_);
|
|
Com::print("-");
|
|
Com::print((long)vol_->blocksPerCluster_);
|
|
Com::println();
|
|
#endif
|
|
|
|
if (blockOfCluster == 0 && blockOffset == 0) {
|
|
// start of new cluster
|
|
if (curCluster_ != 0) {
|
|
uint32_t next;
|
|
if (!vol_->fatGet(curCluster_, &next)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (vol_->isEOC(next)) {
|
|
// add cluster if at end of chain
|
|
if (!addCluster()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
} else {
|
|
curCluster_ = next;
|
|
}
|
|
} else {
|
|
if (firstCluster_ == 0) {
|
|
// allocate first cluster of file
|
|
if (!addCluster()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
} else {
|
|
curCluster_ = firstCluster_;
|
|
}
|
|
}
|
|
}
|
|
// block for data write
|
|
uint32_t block = type_ == FAT_FILE_TYPE_ROOT_FIXED ? vol_->rootDirStart() + (curPosition_ >> 9) : vol_->clusterStartBlock(curCluster_) + blockOfCluster;
|
|
|
|
if (blockOffset != 0 || nToWrite < 512) {
|
|
// partial block - must use cache
|
|
// max space in block
|
|
n = 512 - blockOffset;
|
|
// lesser of space and amount to write
|
|
if (n > nToWrite) n = nToWrite;
|
|
|
|
if (blockOffset == 0 && curPosition_ >= fileSize_) {
|
|
// start of new block don't need to read into cache
|
|
cacheOption = SdVolume::CACHE_RESERVE_FOR_WRITE;
|
|
} else {
|
|
// rewrite part of block
|
|
cacheOption = SdVolume::CACHE_FOR_WRITE;
|
|
}
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("BL:");
|
|
Com::print(block);
|
|
Com::println();
|
|
#endif
|
|
pc = vol_->cacheFetch(block, cacheOption);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
uint8_t* dst = pc->data + blockOffset;
|
|
memcpy(dst, src, n);
|
|
if (512 == (n + blockOffset)) {
|
|
if (!vol_->cacheWriteData()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
} else if (!USE_MULTI_BLOCK_SD_IO || nToWrite < 1024) {
|
|
// use single block write command
|
|
n = 512;
|
|
if (vol_->cacheBlockNumber() == block) {
|
|
vol_->cacheInvalidate();
|
|
}
|
|
if (!vol_->writeBlock(block, src)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
} else {
|
|
// use multiple block write command
|
|
uint8_t maxBlocks = vol_->blocksPerCluster() - blockOfCluster;
|
|
uint8_t nBlock = nToWrite >> 9;
|
|
if (nBlock > maxBlocks) nBlock = maxBlocks;
|
|
|
|
n = 512*nBlock;
|
|
if (!vol_->sdCard()->writeStart(block, nBlock)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
for (uint8_t b = 0; b < nBlock; b++) {
|
|
// invalidate cache if block is in cache
|
|
if ((block + b) == vol_->cacheBlockNumber()) {
|
|
vol_->cacheInvalidate();
|
|
}
|
|
if (!vol_->sdCard()->writeData(src + 512*b)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
if (!vol_->sdCard()->writeStop()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
curPosition_ += n;
|
|
src += n;
|
|
nToWrite -= n;
|
|
}
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("Cur Pos:");
|
|
Com::print(curPosition_);
|
|
Com::println();
|
|
#endif
|
|
|
|
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()) {
|
|
DBG_FAIL_MACRO;
|
|
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
|
|
|
|
// ============== Sd2Card.cpp =============
|
|
|
|
//==============================================================================
|
|
// debug trace macro
|
|
#define SD_TRACE(m, b)
|
|
// #define SD_TRACE(m, b) Serial.print(m);Serial.println(b);
|
|
// SPI functions
|
|
#ifndef SOFTWARE_SPI
|
|
// functions for hardware SPI
|
|
//------------------------------------------------------------------------------
|
|
// make sure SPCR rate is in expected bits
|
|
#if (SPR0 != 0 || SPR1 != 1)
|
|
#error unexpected SPCR bits
|
|
#endif
|
|
//------------------------------------------------------------------------------
|
|
/**
|
|
* initialize SPI pins
|
|
*/
|
|
static void spiBegin() {
|
|
// Already spi init for EEPROM in Alligator boards
|
|
#if !defined(EEPROM_AVAILABLE) || !defined(EEPROM_SPI_ALLIGATOR) || EEPROM_AVAILABLE != EEPROM_SPI_ALLIGATOR
|
|
HAL::spiBegin();
|
|
#endif
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/**
|
|
* Initialize hardware SPI
|
|
* Set SCK rate to F_CPU/pow(2, 1 + spiRate) for spiRate [0,6]
|
|
*/
|
|
static void spiInit(uint8_t spiRate) {
|
|
HAL::spiInit(spiRate);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** SPI receive a byte */
|
|
static uint8_t spiRec() {
|
|
return HAL::spiReceive();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** SPI read data - only one call so force inline */
|
|
static inline __attribute__((always_inline))
|
|
uint8_t spiRec(uint8_t* buf, uint16_t nbyte) {
|
|
HAL::spiReadBlock(buf,nbyte);
|
|
return 0;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** SPI send a byte */
|
|
static void spiSend(uint8_t b) {
|
|
HAL::spiSend(b);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** SPI send block - only one call so force inline */
|
|
static inline __attribute__((always_inline))
|
|
void spiSendBlock(uint8_t token, const uint8_t* buf) {
|
|
HAL::spiSendBlock(token,buf);
|
|
}
|
|
static void spiSend(const uint8_t* buf , size_t n) {
|
|
HAL::spiSend(buf,n);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
#else // SOFTWARE_SPI
|
|
#include <SoftSPI.h>
|
|
static
|
|
SoftSPI<SOFT_SPI_MISO_PIN, SOFT_SPI_MOSI_PIN, SOFT_SPI_SCK_PIN, 0> softSpiBus;
|
|
//------------------------------------------------------------------------------
|
|
/**
|
|
* initialize SPI pins
|
|
*/
|
|
static void spiBegin() {
|
|
softSpiBus.begin();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Soft SPI receive byte */
|
|
static uint8_t spiRec() {
|
|
return softSpiBus.receive();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Soft SPI read data */
|
|
static uint8_t spiRec(uint8_t* buf, size_t nbyte) {
|
|
for (size_t i = 0; i < nbyte; i++) {
|
|
buf[i] = spiRec();
|
|
}
|
|
return 0;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Soft SPI send byte */
|
|
static void spiSend(uint8_t data) {
|
|
softSpiBus.send(data);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Soft SPI send block */
|
|
static void spiSendBlock(uint8_t token, const uint8_t* buf) {
|
|
spiSend(token);
|
|
for (uint16_t i = 0; i < 512; i++) {
|
|
spiSend(buf[i]);
|
|
}
|
|
}
|
|
|
|
#endif // SOFTWARE_SPI
|
|
//==============================================================================
|
|
#if USE_SD_CRC
|
|
// CRC functions
|
|
//------------------------------------------------------------------------------
|
|
static uint8_t CRC7(const uint8_t* data, uint8_t n) {
|
|
uint8_t crc = 0;
|
|
for (uint8_t i = 0; i < n; i++) {
|
|
uint8_t d = data[i];
|
|
for (uint8_t j = 0; j < 8; j++) {
|
|
crc <<= 1;
|
|
if ((d & 0x80) ^ (crc & 0x80)) crc ^= 0x09;
|
|
d <<= 1;
|
|
}
|
|
}
|
|
return (crc << 1) | 1;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
#if USE_SD_CRC == 1
|
|
// slower CRC-CCITT
|
|
// uses the x^16,x^12,x^5,x^1 polynomial.
|
|
static uint16_t CRC_CCITT(const uint8_t *data, size_t n) {
|
|
uint16_t crc = 0;
|
|
for (size_t i = 0; i < n; i++) {
|
|
crc = (uint8_t)(crc >> 8) | (crc << 8);
|
|
crc ^= data[i];
|
|
crc ^= (uint8_t)(crc & 0xff) >> 4;
|
|
crc ^= crc << 12;
|
|
crc ^= (crc & 0xff) << 5;
|
|
}
|
|
return crc;
|
|
}
|
|
#elif USE_SD_CRC > 1 // CRC_CCITT
|
|
//------------------------------------------------------------------------------
|
|
// faster CRC-CCITT
|
|
// uses the x^16,x^12,x^5,x^1 polynomial.
|
|
static const uint16_t crctab[] PROGMEM = {
|
|
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
|
|
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
|
|
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
|
|
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
|
|
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
|
|
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
|
|
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
|
|
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
|
|
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
|
|
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
|
|
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
|
|
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
|
|
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
|
|
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
|
|
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
|
|
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
|
|
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
|
|
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
|
|
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
|
|
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
|
|
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
|
|
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
|
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
|
|
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
|
|
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
|
|
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
|
|
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
|
|
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
|
|
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
|
|
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
|
|
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
|
|
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
|
|
};
|
|
static uint16_t CRC_CCITT(const uint8_t* data, size_t n) {
|
|
uint16_t crc = 0;
|
|
for (size_t i = 0; i < n; i++) {
|
|
crc = pgm_read_word(&crctab[(crc >> 8 ^ data[i]) & 0XFF]) ^ (crc << 8);
|
|
}
|
|
return crc;
|
|
}
|
|
#endif // CRC_CCITT
|
|
#endif // USE_SD_CRC
|
|
//==============================================================================
|
|
// Sd2Card member functions
|
|
//------------------------------------------------------------------------------
|
|
// send command and return error code. Return zero for OK
|
|
uint8_t Sd2Card::cardCommand(uint8_t cmd, uint32_t arg) {
|
|
// select card
|
|
chipSelectLow();
|
|
|
|
// wait up to 300 ms if busy
|
|
waitNotBusy(300);
|
|
|
|
uint8_t *pa = reinterpret_cast<uint8_t *>(&arg);
|
|
|
|
#if USE_SD_CRC
|
|
// form message
|
|
uint8_t d[6] = {static_cast<uint8_t>(cmd | static_cast<uint8_t>(0X40)), pa[3], pa[2], pa[1], pa[0]};
|
|
|
|
// add crc
|
|
d[5] = CRC7(d, 5);
|
|
|
|
// send message
|
|
for (uint8_t k = 0; k < 6; k++) spiSend(d[k]);
|
|
#else // USE_SD_CRC
|
|
// send command
|
|
spiSend(cmd | 0x40);
|
|
|
|
// send argument
|
|
for (int8_t i = 3; i >= 0; i--) spiSend(pa[i]);
|
|
|
|
// send CRC - correct for CMD0 with arg zero or CMD8 with arg 0X1AA
|
|
spiSend(cmd == CMD0 ? 0X95 : 0X87);
|
|
#endif // USE_SD_CRC
|
|
// additional delay for CMD0
|
|
if (cmd == CMD0) delay(100);
|
|
// skip stuff byte for stop read
|
|
if (cmd == CMD12) spiRec();
|
|
|
|
// wait for response
|
|
for (uint8_t i = 0; ((status_ = spiRec()) & 0X80) && i != 0XFF; i++);
|
|
return status_;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/**
|
|
* Determine the size of an SD flash memory card.
|
|
*
|
|
* \return The number of 512 byte data blocks in the card
|
|
* or zero if an error occurs.
|
|
*/
|
|
uint32_t Sd2Card::cardSize() {
|
|
csd_t csd;
|
|
if (!readCSD(&csd)) return 0;
|
|
if (csd.v1.csd_ver == 0) {
|
|
uint8_t read_bl_len = csd.v1.read_bl_len;
|
|
uint16_t c_size = (csd.v1.c_size_high << 10)
|
|
| (csd.v1.c_size_mid << 2) | csd.v1.c_size_low;
|
|
uint8_t c_size_mult = (csd.v1.c_size_mult_high << 1)
|
|
| csd.v1.c_size_mult_low;
|
|
return (uint32_t)(c_size + 1) << (c_size_mult + read_bl_len - 7);
|
|
} else if (csd.v2.csd_ver == 1) {
|
|
uint32_t c_size = 0X10000L * csd.v2.c_size_high + 0X100L
|
|
* (uint32_t)csd.v2.c_size_mid + csd.v2.c_size_low;
|
|
return (c_size + 1) << 10;
|
|
} else {
|
|
error(SD_CARD_ERROR_BAD_CSD);
|
|
return 0;
|
|
}
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void Sd2Card::chipSelectHigh() {
|
|
HAL::digitalWrite(chipSelectPin_, HIGH);
|
|
// insure MISO goes high impedance
|
|
HAL::spiSend(0XFF);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void Sd2Card::chipSelectLow() {
|
|
#ifndef SOFTWARE_SPI
|
|
spiInit(spiRate_);
|
|
#endif // SOFTWARE_SPI
|
|
HAL::digitalWrite(chipSelectPin_, LOW);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Erase a range of blocks.
|
|
*
|
|
* \param[in] firstBlock The address of the first block in the range.
|
|
* \param[in] lastBlock The address of the last block in the range.
|
|
*
|
|
* \note This function requests the SD card to do a flash erase for a
|
|
* range of blocks. The data on the card after an erase operation is
|
|
* either 0 or 1, depends on the card vendor. The card must support
|
|
* single block erase.
|
|
*
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool Sd2Card::erase(uint32_t firstBlock, uint32_t lastBlock) {
|
|
csd_t csd;
|
|
if (!readCSD(&csd)) goto fail;
|
|
// check for single block erase
|
|
if (!csd.v1.erase_blk_en) {
|
|
// erase size mask
|
|
uint8_t m = (csd.v1.sector_size_high << 1) | csd.v1.sector_size_low;
|
|
if ((firstBlock & m) != 0 || ((lastBlock + 1) & m) != 0) {
|
|
// error card can't erase specified area
|
|
error(SD_CARD_ERROR_ERASE_SINGLE_BLOCK);
|
|
goto fail;
|
|
}
|
|
}
|
|
if (type_ != SD_CARD_TYPE_SDHC) {
|
|
firstBlock <<= 9;
|
|
lastBlock <<= 9;
|
|
}
|
|
if (cardCommand(CMD32, firstBlock)
|
|
|| cardCommand(CMD33, lastBlock)
|
|
|| cardCommand(CMD38, 0)) {
|
|
error(SD_CARD_ERROR_ERASE);
|
|
goto fail;
|
|
}
|
|
if (!waitNotBusy(SD_ERASE_TIMEOUT)) {
|
|
error(SD_CARD_ERROR_ERASE_TIMEOUT);
|
|
goto fail;
|
|
}
|
|
chipSelectHigh();
|
|
return true;
|
|
|
|
fail:
|
|
chipSelectHigh();
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Determine if card supports single block erase.
|
|
*
|
|
* \return The value one, true, is returned if single block erase is supported.
|
|
* The value zero, false, is returned if single block erase is not supported.
|
|
*/
|
|
bool Sd2Card::eraseSingleBlockEnable() {
|
|
csd_t csd;
|
|
return readCSD(&csd) ? csd.v1.erase_blk_en : false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/**
|
|
* Initialize an SD flash memory card.
|
|
*
|
|
* \param[in] sckRateID SPI clock rate selector. See setSckRate().
|
|
* \param[in] chipSelectPin SD chip select pin number.
|
|
*
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure. The reason for failure
|
|
* can be determined by calling errorCode() and errorData().
|
|
*/
|
|
bool Sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin) {
|
|
errorCode_ = type_ = 0;
|
|
chipSelectPin_ = chipSelectPin;
|
|
// 16-bit init start time allows over a minute
|
|
uint16_t t0 = (uint16_t)HAL::timeInMilliseconds();
|
|
uint32_t arg;
|
|
|
|
HAL::pinMode(chipSelectPin_, OUTPUT);
|
|
HAL::digitalWrite(chipSelectPin_, HIGH);
|
|
spiBegin();
|
|
|
|
#ifndef SOFTWARE_SPI
|
|
// set SCK rate for initialization commands
|
|
spiRate_ = SPI_SD_INIT_RATE;
|
|
spiInit(spiRate_);
|
|
#endif // SOFTWARE_SPI
|
|
|
|
// must supply min of 74 clock cycles with CS high.
|
|
for (uint8_t i = 0; i < 20; i++) spiSend(0XFF);
|
|
|
|
// command to go idle in SPI mode
|
|
while (cardCommand(CMD0, 0) != R1_IDLE_STATE) {
|
|
if (((uint16_t)HAL::timeInMilliseconds() - t0) > SD_INIT_TIMEOUT) {
|
|
error(SD_CARD_ERROR_CMD0);
|
|
goto fail;
|
|
}
|
|
}
|
|
// check SD version
|
|
while (1) {
|
|
if (cardCommand(CMD8, 0x1AA) == (R1_ILLEGAL_COMMAND | R1_IDLE_STATE)) {
|
|
type(SD_CARD_TYPE_SD1);
|
|
break;
|
|
}
|
|
for (uint8_t i = 0; i < 4; i++) status_ = spiRec();
|
|
if (status_ == 0XAA) {
|
|
type(SD_CARD_TYPE_SD2);
|
|
break;
|
|
}
|
|
if (((uint16_t)HAL::timeInMilliseconds() - t0) > SD_INIT_TIMEOUT) {
|
|
error(SD_CARD_ERROR_CMD8);
|
|
goto fail;
|
|
}
|
|
}
|
|
// initialize card and send host supports SDHC if SD2
|
|
arg = type() == SD_CARD_TYPE_SD2 ? 0X40000000 : 0;
|
|
|
|
while (cardAcmd(ACMD41, arg) != R1_READY_STATE) {
|
|
// check for timeout
|
|
if (((uint16_t)HAL::timeInMilliseconds() - t0) > SD_INIT_TIMEOUT) {
|
|
error(SD_CARD_ERROR_ACMD41);
|
|
goto fail;
|
|
}
|
|
}
|
|
// if SD2 read OCR register to check for SDHC card
|
|
if (type() == SD_CARD_TYPE_SD2) {
|
|
if (cardCommand(CMD58, 0)) {
|
|
error(SD_CARD_ERROR_CMD58);
|
|
goto fail;
|
|
}
|
|
if ((spiRec() & 0XC0) == 0XC0) type(SD_CARD_TYPE_SDHC);
|
|
// discard rest of ocr - contains allowed voltage range
|
|
for (uint8_t i = 0; i < 3; i++) spiRec();
|
|
}
|
|
#if USE_SD_CRC
|
|
if (cardCommand(CMD59, 1) > 1) {
|
|
error(SD_CARD_ERROR_CMD59);
|
|
goto fail;
|
|
}
|
|
#endif // USE_SD_CRC
|
|
chipSelectHigh();
|
|
|
|
#ifndef SOFTWARE_SPI
|
|
return setSckRate(sckRateID);
|
|
#else // SOFTWARE_SPI
|
|
return true;
|
|
#endif // SOFTWARE_SPI
|
|
|
|
fail:
|
|
chipSelectHigh();
|
|
#if defined(DEBUG_SD_ERROR)
|
|
Com::printErrorFLN(PSTR("SD card initalization failed"));
|
|
#endif
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/**
|
|
* Read a 512 byte block from an SD card.
|
|
*
|
|
* \param[in] blockNumber Logical block to be read.
|
|
* \param[out] dst Pointer to the location that will receive the data.
|
|
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool Sd2Card::readBlock(uint32_t blockNumber, uint8_t* dst) {
|
|
SD_TRACE("RB", blockNumber);
|
|
// use address if not SDHC card
|
|
if (type()!= SD_CARD_TYPE_SDHC) blockNumber <<= 9;
|
|
if (cardCommand(CMD17, blockNumber)) {
|
|
error(SD_CARD_ERROR_CMD17);
|
|
goto fail;
|
|
}
|
|
return readData(dst, 512);
|
|
|
|
fail:
|
|
chipSelectHigh();
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Read one data block in a multiple block read sequence
|
|
*
|
|
* \param[in] dst Pointer to the location for the data to be read.
|
|
*
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool Sd2Card::readData(uint8_t *dst) {
|
|
chipSelectLow();
|
|
return readData(dst, 512);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool Sd2Card::readData(uint8_t* dst, size_t count) {
|
|
uint16_t crc;
|
|
// wait for start block token
|
|
uint16_t t0 = HAL::timeInMilliseconds();
|
|
while ((status_ = spiRec()) == 0XFF) {
|
|
if (((uint16_t)HAL::timeInMilliseconds() - t0) > SD_READ_TIMEOUT) {
|
|
error(SD_CARD_ERROR_READ_TIMEOUT);
|
|
goto fail;
|
|
}
|
|
}
|
|
if (status_ != DATA_START_BLOCK) {
|
|
error(SD_CARD_ERROR_READ);
|
|
goto fail;
|
|
}
|
|
// transfer data
|
|
if ((status_ = spiRec(dst, count))) {
|
|
error(SD_CARD_ERROR_SPI_DMA);
|
|
goto fail;
|
|
}
|
|
// get crc
|
|
crc = (spiRec() << 8) | spiRec();
|
|
#if USE_SD_CRC
|
|
if (crc != CRC_CCITT(dst, count)) {
|
|
error(SD_CARD_ERROR_READ_CRC);
|
|
goto fail;
|
|
}
|
|
#endif // USE_SD_CRC
|
|
|
|
chipSelectHigh();
|
|
return true;
|
|
|
|
fail:
|
|
chipSelectHigh();
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** read CID or CSR register */
|
|
bool Sd2Card::readRegister(uint8_t cmd, void* buf) {
|
|
uint8_t* dst = reinterpret_cast<uint8_t*>(buf);
|
|
if (cardCommand(cmd, 0)) {
|
|
error(SD_CARD_ERROR_READ_REG);
|
|
goto fail;
|
|
}
|
|
return readData(dst, 16);
|
|
|
|
fail:
|
|
chipSelectHigh();
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Start a read multiple blocks sequence.
|
|
*
|
|
* \param[in] blockNumber Address of first block in sequence.
|
|
*
|
|
* \note This function is used with readData() and readStop() for optimized
|
|
* multiple block reads. SPI chipSelect must be low for the entire sequence.
|
|
*
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool Sd2Card::readStart(uint32_t blockNumber) {
|
|
SD_TRACE("RS", blockNumber);
|
|
if (type()!= SD_CARD_TYPE_SDHC) blockNumber <<= 9;
|
|
if (cardCommand(CMD18, blockNumber)) {
|
|
error(SD_CARD_ERROR_CMD18);
|
|
goto fail;
|
|
}
|
|
chipSelectHigh();
|
|
return true;
|
|
|
|
fail:
|
|
chipSelectHigh();
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** End a read multiple blocks sequence.
|
|
*
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool Sd2Card::readStop() {
|
|
chipSelectLow();
|
|
if (cardCommand(CMD12, 0)) {
|
|
error(SD_CARD_ERROR_CMD12);
|
|
goto fail;
|
|
}
|
|
chipSelectHigh();
|
|
return true;
|
|
|
|
fail:
|
|
chipSelectHigh();
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/**
|
|
* Set the SPI clock rate.
|
|
*
|
|
* \param[in] sckRateID A value in the range [0, 14].
|
|
*
|
|
* The SPI clock divisor will be set to approximately
|
|
*
|
|
* (2 + (sckRateID & 1)) << ( sckRateID/2)
|
|
*
|
|
* The maximum SPI rate is F_CPU/2 for \a sckRateID = 0 and the rate is
|
|
* F_CPU/128 for \a scsRateID = 12.
|
|
*
|
|
* \return The value one, true, is returned for success and the value zero,
|
|
* false, is returned for an invalid value of \a sckRateID.
|
|
*/
|
|
bool Sd2Card::setSckRate(uint8_t sckRateID) {
|
|
if (sckRateID > MAX_SCK_RATE_ID) {
|
|
error(SD_CARD_ERROR_SCK_RATE);
|
|
return false;
|
|
}
|
|
spiRate_ = sckRateID;
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// wait for card to go not busy
|
|
bool Sd2Card::waitNotBusy(uint16_t timeoutMillis) {
|
|
uint16_t t0 = HAL::timeInMilliseconds();
|
|
while (spiRec() != 0XFF) {
|
|
if (((uint16_t)HAL::timeInMilliseconds() - t0) >= timeoutMillis) goto fail;
|
|
}
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/**
|
|
* Writes a 512 byte block to an SD card.
|
|
*
|
|
* \param[in] blockNumber Logical block to be written.
|
|
* \param[in] src Pointer to the location of the data to be written.
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool Sd2Card::writeBlock(uint32_t blockNumber, const uint8_t* src) {
|
|
SD_TRACE("WB", blockNumber);
|
|
// use address if not SDHC card
|
|
if (type() != SD_CARD_TYPE_SDHC) blockNumber <<= 9;
|
|
if (cardCommand(CMD24, blockNumber)) {
|
|
error(SD_CARD_ERROR_CMD24);
|
|
goto fail;
|
|
}
|
|
if (!writeData(DATA_START_BLOCK, src)) goto fail;
|
|
|
|
// wait for flash programming to complete
|
|
if (!waitNotBusy(SD_WRITE_TIMEOUT)) {
|
|
error(SD_CARD_ERROR_WRITE_TIMEOUT);
|
|
goto fail;
|
|
}
|
|
// response is r2 so get and check two bytes for nonzero
|
|
if (cardCommand(CMD13, 0) || spiRec()) {
|
|
error(SD_CARD_ERROR_WRITE_PROGRAMMING);
|
|
goto fail;
|
|
}
|
|
chipSelectHigh();
|
|
return true;
|
|
|
|
fail:
|
|
chipSelectHigh();
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Write one data block in a multiple block write sequence
|
|
* \param[in] src Pointer to the location of the data to be written.
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool Sd2Card::writeData(const uint8_t* src) {
|
|
chipSelectLow();
|
|
// wait for previous write to finish
|
|
if (!waitNotBusy(SD_WRITE_TIMEOUT)) goto fail;
|
|
if (!writeData(WRITE_MULTIPLE_TOKEN, src)) goto fail;
|
|
chipSelectHigh();
|
|
return true;
|
|
|
|
fail:
|
|
error(SD_CARD_ERROR_WRITE_MULTIPLE);
|
|
chipSelectHigh();
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
// send one block of data for write block or write multiple blocks
|
|
bool Sd2Card::writeData(uint8_t token, const uint8_t* src) {
|
|
#if USE_SD_CRC
|
|
uint16_t crc = CRC_CCITT(src, 512);
|
|
#else // USE_SD_CRC
|
|
uint16_t crc = 0XFFFF;
|
|
#endif // USE_SD_CRC
|
|
|
|
spiSend(token);
|
|
spiSend(src, 512);
|
|
spiSend(crc >> 8);
|
|
spiSend(crc & 0XFF);
|
|
|
|
status_ = spiRec();
|
|
if ((status_ & DATA_RES_MASK) != DATA_RES_ACCEPTED) {
|
|
error(SD_CARD_ERROR_WRITE);
|
|
goto fail;
|
|
}
|
|
return true;
|
|
|
|
fail:
|
|
chipSelectHigh();
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Start a write multiple blocks sequence.
|
|
*
|
|
* \param[in] blockNumber Address of first block in sequence.
|
|
* \param[in] eraseCount The number of blocks to be pre-erased.
|
|
*
|
|
* \note This function is used with writeData() and writeStop()
|
|
* for optimized multiple block writes.
|
|
*
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool Sd2Card::writeStart(uint32_t blockNumber, uint32_t eraseCount) {
|
|
SD_TRACE("WS", blockNumber);
|
|
// send pre-erase count
|
|
if (cardAcmd(ACMD23, eraseCount)) {
|
|
error(SD_CARD_ERROR_ACMD23);
|
|
goto fail;
|
|
}
|
|
// use address if not SDHC card
|
|
if (type() != SD_CARD_TYPE_SDHC) blockNumber <<= 9;
|
|
if (cardCommand(CMD25, blockNumber)) {
|
|
error(SD_CARD_ERROR_CMD25);
|
|
goto fail;
|
|
}
|
|
chipSelectHigh();
|
|
return true;
|
|
|
|
fail:
|
|
chipSelectHigh();
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** End a write multiple blocks sequence.
|
|
*
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure.
|
|
*/
|
|
bool Sd2Card::writeStop() {
|
|
chipSelectLow();
|
|
if (!waitNotBusy(SD_WRITE_TIMEOUT)) goto fail;
|
|
spiSend(STOP_TRAN_TOKEN);
|
|
if (!waitNotBusy(SD_WRITE_TIMEOUT)) goto fail;
|
|
chipSelectHigh();
|
|
return true;
|
|
|
|
fail:
|
|
error(SD_CARD_ERROR_STOP_TRAN);
|
|
chipSelectHigh();
|
|
return false;
|
|
}
|
|
|
|
// =================== SdVolume ===================
|
|
|
|
//------------------------------------------------------------------------------
|
|
#if !USE_MULTIPLE_CARDS
|
|
// raw block cache
|
|
|
|
cache_t SdVolume::cacheBuffer_; // 512 byte cache for Sd2Card
|
|
uint32_t SdVolume::cacheBlockNumber_; // current block number
|
|
uint8_t SdVolume::cacheStatus_; // status of cache block
|
|
uint32_t SdVolume::cacheFatOffset_; // offset for mirrored FAT
|
|
#if USE_SEPARATE_FAT_CACHE
|
|
cache_t SdVolume::cacheFatBuffer_; // 512 byte cache for FAT
|
|
uint32_t SdVolume::cacheFatBlockNumber_; // current Fat block number
|
|
uint8_t SdVolume::cacheFatStatus_; // status of cache Fatblock
|
|
#endif // USE_SEPARATE_FAT_CACHE
|
|
Sd2Card* SdVolume::sdCard_; // pointer to SD card object
|
|
#endif // USE_MULTIPLE_CARDS
|
|
//------------------------------------------------------------------------------
|
|
// find a contiguous group of clusters
|
|
bool SdVolume::allocContiguous(uint32_t count, uint32_t* curCluster) {
|
|
// start of group
|
|
uint32_t bgnCluster;
|
|
// end of group
|
|
uint32_t endCluster;
|
|
// last cluster of FAT
|
|
uint32_t fatEnd = clusterCount_ + 1;
|
|
|
|
// flag to save place to start next search
|
|
bool setStart;
|
|
|
|
// set search start cluster
|
|
if (*curCluster) {
|
|
// try to make file contiguous
|
|
bgnCluster = *curCluster + 1;
|
|
|
|
// don't save new start location
|
|
setStart = false;
|
|
} else {
|
|
// start at likely place for free cluster
|
|
bgnCluster = allocSearchStart_;
|
|
|
|
// save next search start if one cluster
|
|
setStart = count == 1;
|
|
}
|
|
// end of group
|
|
endCluster = bgnCluster;
|
|
|
|
// search the FAT for free clusters
|
|
for (uint32_t n = 0;; n++, endCluster++) {
|
|
// can't find space checked all clusters
|
|
if (n >= clusterCount_) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
|
|
// past end - start from beginning of FAT
|
|
if (endCluster > fatEnd) {
|
|
bgnCluster = endCluster = 2;
|
|
}
|
|
uint32_t f;
|
|
if (!fatGet(endCluster, &f)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
|
|
if (f != 0) {
|
|
// cluster in use try next cluster as bgnCluster
|
|
bgnCluster = endCluster + 1;
|
|
} else if ((endCluster - bgnCluster + 1) == count) {
|
|
// done - found space
|
|
break;
|
|
}
|
|
}
|
|
// mark end of chain
|
|
if (!fatPutEOC(endCluster)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
|
|
// link clusters
|
|
while (endCluster > bgnCluster) {
|
|
if (!fatPut(endCluster - 1, endCluster)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
endCluster--;
|
|
}
|
|
if (*curCluster != 0) {
|
|
// connect chains
|
|
if (!fatPut(*curCluster, bgnCluster)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
// return first cluster number to caller
|
|
*curCluster = bgnCluster;
|
|
// remember possible next free cluster
|
|
if (setStart) allocSearchStart_ = bgnCluster + 1;
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//==============================================================================
|
|
|
|
// cache functions
|
|
|
|
#if USE_SEPARATE_FAT_CACHE
|
|
|
|
//------------------------------------------------------------------------------
|
|
cache_t* SdVolume::cacheFetch(uint32_t blockNumber, uint8_t options) {
|
|
return cacheFetchData(blockNumber, options);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
cache_t* SdVolume::cacheFetchData(uint32_t blockNumber, uint8_t options) {
|
|
if (cacheBlockNumber_ != blockNumber) {
|
|
if (!cacheWriteData()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (!(options & CACHE_OPTION_NO_READ)) {
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("Rd blk:");
|
|
Com::print(blockNumber);
|
|
Com::println();
|
|
#endif
|
|
if (!sdCard_->readBlock(blockNumber, cacheBuffer_.data)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
cacheStatus_ = 0;
|
|
cacheBlockNumber_ = blockNumber;
|
|
}
|
|
cacheStatus_ |= options & CACHE_STATUS_MASK;
|
|
return &cacheBuffer_;
|
|
|
|
fail:
|
|
return 0;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
cache_t* SdVolume::cacheFetchFat(uint32_t blockNumber, uint8_t options) {
|
|
if (cacheFatBlockNumber_ != blockNumber) {
|
|
if (!cacheWriteFat()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (!(options & CACHE_OPTION_NO_READ)) {
|
|
if (!sdCard_->readBlock(blockNumber, cacheFatBuffer_.data)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
cacheFatStatus_ = 0;
|
|
cacheFatBlockNumber_ = blockNumber;
|
|
}
|
|
cacheFatStatus_ |= options & CACHE_STATUS_MASK;
|
|
return &cacheFatBuffer_;
|
|
|
|
fail:
|
|
return 0;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool SdVolume::cacheSync() {
|
|
return cacheWriteData() && cacheWriteFat();
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool SdVolume::cacheWriteData() {
|
|
if (cacheStatus_ & CACHE_STATUS_DIRTY) {
|
|
if (!sdCard_->writeBlock(cacheBlockNumber_, cacheBuffer_.data)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
cacheStatus_ &= ~CACHE_STATUS_DIRTY;
|
|
}
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool SdVolume::cacheWriteFat() {
|
|
if (cacheFatStatus_ & CACHE_STATUS_DIRTY) {
|
|
if (!sdCard_->writeBlock(cacheFatBlockNumber_, cacheFatBuffer_.data)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// mirror second FAT
|
|
if (cacheFatOffset_) {
|
|
uint32_t lbn = cacheFatBlockNumber_ + cacheFatOffset_;
|
|
if (!sdCard_->writeBlock(lbn, cacheFatBuffer_.data)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
cacheFatStatus_ &= ~CACHE_STATUS_DIRTY;
|
|
}
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
#else // USE_SEPARATE_FAT_CACHE
|
|
//------------------------------------------------------------------------------
|
|
cache_t* SdVolume::cacheFetch(uint32_t blockNumber, uint8_t options) {
|
|
if (cacheBlockNumber_ != blockNumber) {
|
|
if (!cacheSync()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (!(options & CACHE_OPTION_NO_READ)) {
|
|
if (!sdCard_->readBlock(blockNumber, cacheBuffer_.data)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
cacheStatus_ = 0;
|
|
cacheBlockNumber_ = blockNumber;
|
|
}
|
|
cacheStatus_ |= options & CACHE_STATUS_MASK;
|
|
return &cacheBuffer_;
|
|
|
|
fail:
|
|
return 0;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
cache_t* SdVolume::cacheFetchFat(uint32_t blockNumber, uint8_t options) {
|
|
return cacheFetch(blockNumber, options | CACHE_STATUS_FAT_BLOCK);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool SdVolume::cacheSync() {
|
|
if (cacheStatus_ & CACHE_STATUS_DIRTY) {
|
|
#ifdef GLENN_DEBUG
|
|
Com::print("Wr blk:");
|
|
Com::print(cacheBlockNumber_);
|
|
Com::println();
|
|
#endif
|
|
if (!sdCard_->writeBlock(cacheBlockNumber_, cacheBuffer_.data)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// mirror second FAT
|
|
if ((cacheStatus_ & CACHE_STATUS_FAT_BLOCK) && cacheFatOffset_) {
|
|
uint32_t lbn = cacheBlockNumber_ + cacheFatOffset_;
|
|
if (!sdCard_->writeBlock(lbn, cacheBuffer_.data)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
cacheStatus_ &= ~CACHE_STATUS_DIRTY;
|
|
}
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool SdVolume::cacheWriteData() {
|
|
return cacheSync();
|
|
}
|
|
#endif // USE_SEPARATE_FAT_CACHE
|
|
//------------------------------------------------------------------------------
|
|
void SdVolume::cacheInvalidate() {
|
|
cacheBlockNumber_ = 0XFFFFFFFF;
|
|
cacheStatus_ = 0;
|
|
}
|
|
//==============================================================================
|
|
//------------------------------------------------------------------------------
|
|
uint32_t SdVolume::clusterStartBlock(uint32_t cluster) const {
|
|
return dataStartBlock_ + ((cluster - 2)*blocksPerCluster_);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
// Fetch a FAT entry
|
|
bool SdVolume::fatGet(uint32_t cluster, uint32_t* value) {
|
|
uint32_t lba;
|
|
cache_t* pc;
|
|
|
|
// error if reserved cluster of beyond FAT
|
|
|
|
if (cluster < 2 || cluster > (clusterCount_ + 1)) {
|
|
|
|
DBG_FAIL_MACRO;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
if (FAT12_SUPPORT && fatType_ == 12) {
|
|
|
|
uint16_t index = cluster;
|
|
|
|
index += index >> 1;
|
|
|
|
lba = fatStartBlock_ + (index >> 9);
|
|
pc = cacheFetchFat(lba, CACHE_FOR_READ);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
index &= 0X1FF;
|
|
uint16_t tmp = pc->data[index];
|
|
index++;
|
|
if (index == 512) {
|
|
pc = cacheFetchFat(lba + 1, CACHE_FOR_READ);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
index = 0;
|
|
}
|
|
tmp |= pc->data[index] << 8;
|
|
*value = cluster & 1 ? tmp >> 4 : tmp & 0XFFF;
|
|
return true;
|
|
}
|
|
if (fatType_ == 16) {
|
|
lba = fatStartBlock_ + (cluster >> 8);
|
|
} else if (fatType_ == 32) {
|
|
lba = fatStartBlock_ + (cluster >> 7);
|
|
} else {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
pc = cacheFetchFat(lba, CACHE_FOR_READ);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (fatType_ == 16) {
|
|
*value = pc->fat16[cluster & 0XFF];
|
|
} else {
|
|
*value = pc->fat32[cluster & 0X7F] & FAT32MASK;
|
|
}
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
// Store a FAT entry
|
|
bool SdVolume::fatPut(uint32_t cluster, uint32_t value) {
|
|
uint32_t lba;
|
|
cache_t* pc;
|
|
// error if reserved cluster of beyond FAT
|
|
if (cluster < 2 || cluster > (clusterCount_ + 1)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (FAT12_SUPPORT && fatType_ == 12) {
|
|
uint16_t index = cluster;
|
|
index += index >> 1;
|
|
lba = fatStartBlock_ + (index >> 9);
|
|
pc = cacheFetchFat(lba, CACHE_FOR_WRITE);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
index &= 0X1FF;
|
|
uint8_t tmp = value;
|
|
if (cluster & 1) {
|
|
tmp = (pc->data[index] & 0XF) | tmp << 4;
|
|
}
|
|
pc->data[index] = tmp;
|
|
|
|
index++;
|
|
if (index == 512) {
|
|
lba++;
|
|
index = 0;
|
|
pc = cacheFetchFat(lba, CACHE_FOR_WRITE);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
tmp = value >> 4;
|
|
if (!(cluster & 1)) {
|
|
tmp = ((pc->data[index] & 0XF0)) | tmp >> 4;
|
|
}
|
|
pc->data[index] = tmp;
|
|
return true;
|
|
}
|
|
if (fatType_ == 16) {
|
|
lba = fatStartBlock_ + (cluster >> 8);
|
|
} else if (fatType_ == 32) {
|
|
lba = fatStartBlock_ + (cluster >> 7);
|
|
} else {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
pc = cacheFetchFat(lba, CACHE_FOR_WRITE);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// store entry
|
|
if (fatType_ == 16) {
|
|
pc->fat16[cluster & 0XFF] = value;
|
|
} else {
|
|
pc->fat32[cluster & 0X7F] = value;
|
|
}
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
// free a cluster chain
|
|
bool SdVolume::freeChain(uint32_t cluster) {
|
|
uint32_t next;
|
|
|
|
// clear free cluster location
|
|
allocSearchStart_ = 2;
|
|
|
|
do {
|
|
if (!fatGet(cluster, &next)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// free cluster
|
|
if (!fatPut(cluster, 0)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
|
|
cluster = next;
|
|
} while (!isEOC(cluster));
|
|
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Volume free space in clusters.
|
|
*
|
|
* \return Count of free clusters for success or -1 if an error occurs.
|
|
*/
|
|
int32_t SdVolume::freeClusterCount() {
|
|
uint32_t free = 0;
|
|
uint32_t lba;
|
|
uint32_t todo = clusterCount_ + 2;
|
|
uint16_t n;
|
|
|
|
if (FAT12_SUPPORT && fatType_ == 12) {
|
|
for (unsigned i = 2; i < todo; i++) {
|
|
uint32_t c;
|
|
if (!fatGet(i, &c)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (c == 0) free++;
|
|
}
|
|
} else if (fatType_ == 16 || fatType_ == 32) {
|
|
lba = fatStartBlock_;
|
|
while (todo) {
|
|
cache_t* pc = cacheFetchFat(lba++, CACHE_FOR_READ);
|
|
if (!pc) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
n = fatType_ == 16 ? 256 : 128;
|
|
if (todo < n) n = todo;
|
|
if (fatType_ == 16) {
|
|
for (uint16_t i = 0; i < n; i++) {
|
|
if (pc->fat16[i] == 0) free++;
|
|
}
|
|
} else {
|
|
for (uint16_t i = 0; i < n; i++) {
|
|
if (pc->fat32[i] == 0) free++;
|
|
}
|
|
}
|
|
todo -= n;
|
|
}
|
|
} else {
|
|
// invalid FAT type
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
return free;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Initialize a FAT volume.
|
|
*
|
|
* \param[in] dev The SD card where the volume is located.
|
|
*
|
|
* \param[in] part The partition to be used. Legal values for \a part are
|
|
* 1-4 to use the corresponding partition on a device formatted with
|
|
* a MBR, Master Boot Record, or zero if the device is formatted as
|
|
* a super floppy with the FAT boot sector in block zero.
|
|
*
|
|
* \return The value one, true, is returned for success and
|
|
* the value zero, false, is returned for failure. Reasons for
|
|
* failure include not finding a valid partition, not finding a valid
|
|
* FAT file system in the specified partition or an I/O error.
|
|
*/
|
|
bool SdVolume::init(Sd2Card* dev, uint8_t part) {
|
|
uint32_t totalBlocks;
|
|
uint32_t volumeStartBlock = 0;
|
|
fat32_boot_t* fbs;
|
|
cache_t* pc;
|
|
sdCard_ = dev;
|
|
fatType_ = 0;
|
|
allocSearchStart_ = 2;
|
|
cacheStatus_ = 0; // cacheSync() will write block if true
|
|
cacheBlockNumber_ = 0XFFFFFFFF;
|
|
cacheFatOffset_ = 0;
|
|
#if defined(USE_SERARATEFAT_CACHE) && USE_SERARATEFAT_CACHE
|
|
cacheFatStatus_ = 0; // cacheSync() will write block if true
|
|
cacheFatBlockNumber_ = 0XFFFFFFFF;
|
|
#endif // USE_SERARATEFAT_CACHE
|
|
// if part == 0 assume super floppy with FAT boot sector in block zero
|
|
// if part > 0 assume mbr volume with partition table
|
|
if (part) {
|
|
if (part > 4) {
|
|
#if defined(DEBUG_SD_ERROR)
|
|
Com::printErrorFLN(PSTR("volume init: illegal part"));
|
|
#endif
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
pc = cacheFetch(volumeStartBlock, CACHE_FOR_READ);
|
|
if (!pc) {
|
|
#if defined(DEBUG_SD_ERROR)
|
|
Com::printErrorFLN(PSTR("volume init: cache fetch failed"));
|
|
#endif
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
part_t* p = &pc->mbr.part[part-1];
|
|
if ((p->boot & 0X7F) !=0 ||
|
|
p->totalSectors < 100 ||
|
|
p->firstSector == 0) {
|
|
// not a valid partition
|
|
#if defined(DEBUG_SD_ERROR)
|
|
Com::printErrorFLN(PSTR("volume init: invalid partition"));
|
|
#endif
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
volumeStartBlock = p->firstSector;
|
|
}
|
|
pc = cacheFetch(volumeStartBlock, CACHE_FOR_READ);
|
|
if (!pc) {
|
|
#if defined(DEBUG_SD_ERROR)
|
|
Com::printErrorFLN(PSTR("volume init: cache fetch failed"));
|
|
#endif
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
fbs = &(pc->fbs32);
|
|
if (fbs->bytesPerSector != 512 ||
|
|
fbs->fatCount == 0 ||
|
|
fbs->reservedSectorCount == 0 ||
|
|
fbs->sectorsPerCluster == 0) {
|
|
// not valid FAT volume
|
|
#if defined(DEBUG_SD_ERROR)
|
|
Com::printErrorFLN(PSTR("volume init: not a valid FAT volume"));
|
|
Com::printFLN(PSTR("BytesPerSector:"),fbs->bytesPerSector);
|
|
Com::printFLN(PSTR("fatCount:"),fbs->fatCount);
|
|
Com::printFLN(PSTR("reservedSectorCount:"),fbs->reservedSectorCount);
|
|
Com::printFLN(PSTR("sectorsPerCluster:"),fbs->sectorsPerCluster);
|
|
#endif
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
fatCount_ = fbs->fatCount;
|
|
blocksPerCluster_ = fbs->sectorsPerCluster;
|
|
// determine shift that is same as multiply by blocksPerCluster_
|
|
clusterSizeShift_ = 0;
|
|
while (blocksPerCluster_ != (1 << clusterSizeShift_)) {
|
|
// error if not power of 2
|
|
if (clusterSizeShift_++ > 7) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
blocksPerFat_ = fbs->sectorsPerFat16 ?
|
|
fbs->sectorsPerFat16 : fbs->sectorsPerFat32;
|
|
|
|
if (fatCount_ > 0) cacheFatOffset_ = blocksPerFat_;
|
|
fatStartBlock_ = volumeStartBlock + fbs->reservedSectorCount;
|
|
|
|
// count for FAT16 zero for FAT32
|
|
rootDirEntryCount_ = fbs->rootDirEntryCount;
|
|
|
|
// directory start for FAT16 dataStart for FAT32
|
|
rootDirStart_ = fatStartBlock_ + fbs->fatCount * blocksPerFat_;
|
|
|
|
// data start for FAT16 and FAT32
|
|
dataStartBlock_ = rootDirStart_ + ((32 * fbs->rootDirEntryCount + 511)/512);
|
|
|
|
// total blocks for FAT16 or FAT32
|
|
totalBlocks = fbs->totalSectors16 ?
|
|
fbs->totalSectors16 : fbs->totalSectors32;
|
|
// total data blocks
|
|
clusterCount_ = totalBlocks - (dataStartBlock_ - volumeStartBlock);
|
|
|
|
// divide by cluster size to get cluster count
|
|
clusterCount_ >>= clusterSizeShift_;
|
|
|
|
// FAT type is determined by cluster count
|
|
if (clusterCount_ < 4085) {
|
|
fatType_ = 12;
|
|
if (!FAT12_SUPPORT) {
|
|
#if defined(DEBUG_SD_ERROR)
|
|
Com::printErrorFLN(PSTR("volume init: No FAT 12 support"));
|
|
#endif
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
} else if (clusterCount_ < 65525) {
|
|
fatType_ = 16;
|
|
} else {
|
|
rootDirStart_ = fbs->fat32RootCluster;
|
|
fatType_ = 32;
|
|
}
|
|
return true;
|
|
|
|
fail:
|
|
#if defined(DEBUG_SD_ERROR)
|
|
Com::printErrorFLN(PSTR("SD volume open failed"));
|
|
#endif
|
|
return false;
|
|
}
|
|
// =============== SdFile.cpp ====================
|
|
|
|
/** 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).
|
|
*/
|
|
SdFile::SdFile(const char* path, uint8_t oflag) : SdBaseFile(path, oflag) {
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** 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.
|
|
*
|
|
*/
|
|
int SdFile::write(const void* buf, size_t nbyte) {
|
|
return SdBaseFile::write(buf, nbyte);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Write a byte to a file. Required by the Arduino Print class.
|
|
* \param[in] b the byte to be written.
|
|
* Use getWriteError to check for errors.
|
|
* \return 1 for success and 0 for failure.
|
|
*/
|
|
#ifdef COMPAT_PRE1
|
|
void SdFile::write(uint8_t b) {
|
|
SdBaseFile::write(&b, 1);
|
|
}
|
|
#else
|
|
size_t SdFile::write(uint8_t b) {
|
|
return SdBaseFile::write(&b, 1) == 1 ? 1 : 0;
|
|
}
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
/** Write a string to a file. Used by the Arduino Print class.
|
|
* \param[in] str Pointer to the string.
|
|
* Use getWriteError to check for errors.
|
|
* \return count of characters written for success or -1 for failure.
|
|
*/
|
|
int SdFile::write(const char* str) {
|
|
return SdBaseFile::write(str, strlen(str));
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Write a PROGMEM string to a file.
|
|
* \param[in] str Pointer to the PROGMEM string.
|
|
* Use getWriteError to check for errors.
|
|
*/
|
|
void SdFile::write_P(FSTRINGPARAM(str)) {
|
|
for (uint8_t c; (c = HAL::readFlashByte(str)); str++) write(c);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
/** Write a PROGMEM string followed by CR/LF to a file.
|
|
* \param[in] str Pointer to the PROGMEM string.
|
|
* Use getWriteError to check for errors.
|
|
*/
|
|
void SdFile::writeln_P(FSTRINGPARAM(str)) {
|
|
write_P(str);
|
|
write_P(Com::tNewline);
|
|
}
|
|
|
|
// ================ SdFatUtil.cpp ===================
|
|
|
|
//------------------------------------------------------------------------------
|
|
/** 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 to Serial.
|
|
*
|
|
* \param[in] str Pointer to string stored in flash memory.
|
|
*/
|
|
void SdFatUtil::SerialPrint_P(FSTRINGPARAM(str)) {
|
|
Com::printF(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(FSTRINGPARAM(str)) {
|
|
Com::printFLN(str);
|
|
}
|
|
|
|
// ==============
|
|
|
|
#endif // SDSUPPORT
|
|
|