mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-19 23:22:16 +03:00
SD Filesystem compatible with 8266 File, using latest SdFat (#5525)
* Add a FAT filesystem for SD cards to Arduino FS Arduino forked a copy of SD lib several years ago, put their own wrapper around it, and it's been languishing in our ESP8266 libraries ever since as SD. It doesn't support long file names, has class names which conflict with the ESP8266 internal names, and hasn't been updated in ages. The original author of the SD library has continued work in the meantime, and produced a very feature rich implementation of SdFat. It unfortunately also conflicts with the class names we use in ESP8266 Arduino and has a different API than the internal SPIFFS or proposed LittleFS filesystem objects. This PR puts a wrapper around the latest and greatest SdFat library, by forking it and wrapping its classes in a private namespace "sdfat," and making as thin a wrapper as possible around it to conform to the ESP8266 FS, File, and Dir classes. This PR also removes the Arduino SD.h class library and rewrites it using the new SDFS filesystem to make everything in the ESP8266 Arduino core compatible with each other. By doing so it lets us use a single interface for anything needing a file instead of multiple ones (see SDWebServer and how a different object is needed vs. one serving from SPIFFS even though the logic is all the same). Same for BearSSL's CertStores and probably a few others I've missed, cleaning up our code base significantly. Like LittleFS, silently create directories when a file is created with a subdirectory specifier ("/path/to/file.txt") if they do not yet exist. Adds a blacklist of sketches to skip in the CI process (because SdFat has many examples which do not build properly on the ESP8266). Now that LittleFS and SDFS have directory support, the FS needs to be able to communicate whether a name is one or the other. Add a simple bool FS::isDirectory() and bool FS::isFile() method. SPIFFS doesn't have directories, so if it's valid it's a file and reported as such. Add ::mkdir/::rmdir to the FS class to allow users to make and destroy subdirectories. SPIFFS directory operations will, of course, fail and return false. Emulate a 16MB SD card and allow test runner to exercise it by using a custom SdFat HOST_MOCK-enabled object. Throw out the original Arduino SD.h class and rewrite from scratch using only the ESP8266 native SDFS calls. This makes "SD" based applications compatible with normal ESP8266 "File" and "FS" and "SPIFFS" operations. The only major visible change for users is that long filenames now are fully supported and work without any code changes. If there are static arrays of 11 bytes for old 8.3 names in code, they will need to be adjusted. While it is recommended to use the more powerful SDFS class to access SD cards, this SD.h wrapper allows for use of existing Arduino libraries which are built to only with with that SD class. Additional helper functions added to ESP8266 native Filesystem:: classes to help support this portability. The rewrite is good enough to run the original SDWebServer and SD example code without any changes. * Add a FSConfig and SDFSConfig param to FS.begin() Allows for configuration values to be passed into a filesystem via the begin method. By default, a FS will receive a nullptr and should so whatever is appropriate. The base FSConfig class has one parameter, _autoFormat, set by the default constructor to true. For SPIFFS, you can now disable auto formatting on mount failure by passing in a FSConfig(false) object. For SDFS a SDFSConfig parameter can be passed into config specifying the chip select and SPI configuration. If nothing is passed in, the begin will fail since there are no safe default values here. * Add FS::setConfig to set FS-specific options Add a new call, FS::setConfig(const {SDFS,SPIFFS}Config *cfg), which takes a FS-specific configuration object and copies any special settings on a per-FS basis. The call is only valid on unmounted filesystems, and checks the type of object passed in matches the FS being configured. Updates the docs and tests to utilize this new configuration method. * Add ::truncate to File interface Fixes #3846 * Use polledTimeout for formatting yields, cleanup Use the new polledTimeout class to ensure a yield every 5ms while formatting. Add in default case handling and some debug messages when invalid inputs specified. * Make setConfig take const& ref, cleaner code setConfig now can take a parameter defined directly in the call by using a const &ref to it, leading to one less line of code to write and cleaner reading of the code. Also clean up SDFS implementation pointer definition.
This commit is contained in:
parent
61a8a6b14e
commit
b1da9eda46
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -7,6 +7,9 @@
|
|||||||
[submodule "libraries/SoftwareSerial"]
|
[submodule "libraries/SoftwareSerial"]
|
||||||
path = libraries/SoftwareSerial
|
path = libraries/SoftwareSerial
|
||||||
url = https://github.com/plerup/espsoftwareserial.git
|
url = https://github.com/plerup/espsoftwareserial.git
|
||||||
|
[submodule "libraries/ESP8266SdFat"]
|
||||||
|
path = libraries/ESP8266SdFat
|
||||||
|
url = https://github.com/earlephilhower/ESP8266SdFat.git
|
||||||
[submodule "tools/pyserial"]
|
[submodule "tools/pyserial"]
|
||||||
path = tools/pyserial
|
path = tools/pyserial
|
||||||
url = https://github.com/pyserial/pyserial.git
|
url = https://github.com/pyserial/pyserial.git
|
||||||
|
@ -114,6 +114,13 @@ File::operator bool() const {
|
|||||||
return !!_p;
|
return !!_p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool File::truncate(uint32_t size) {
|
||||||
|
if (!_p)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _p->truncate(size);
|
||||||
|
}
|
||||||
|
|
||||||
const char* File::name() const {
|
const char* File::name() const {
|
||||||
if (!_p)
|
if (!_p)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -121,6 +128,43 @@ const char* File::name() const {
|
|||||||
return _p->name();
|
return _p->name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* File::fullName() const {
|
||||||
|
if (!_p)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return _p->fullName();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool File::isFile() const {
|
||||||
|
if (!_p)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _p->isFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool File::isDirectory() const {
|
||||||
|
if (!_p)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _p->isDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
void File::rewindDirectory() {
|
||||||
|
if (!_fakeDir) {
|
||||||
|
_fakeDir = std::make_shared<Dir>(_baseFS->openDir(fullName()));
|
||||||
|
} else {
|
||||||
|
_fakeDir->rewind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File File::openNextFile() {
|
||||||
|
if (!_fakeDir) {
|
||||||
|
_fakeDir = std::make_shared<Dir>(_baseFS->openDir(fullName()));
|
||||||
|
}
|
||||||
|
_fakeDir->next();
|
||||||
|
return _fakeDir->openFile("r");
|
||||||
|
}
|
||||||
|
|
||||||
String File::readString()
|
String File::readString()
|
||||||
{
|
{
|
||||||
String ret;
|
String ret;
|
||||||
@ -148,7 +192,7 @@ File Dir::openFile(const char* mode) {
|
|||||||
return File();
|
return File();
|
||||||
}
|
}
|
||||||
|
|
||||||
return File(_impl->openFile(om, am));
|
return File(_impl->openFile(om, am), _baseFS);
|
||||||
}
|
}
|
||||||
|
|
||||||
String Dir::fileName() {
|
String Dir::fileName() {
|
||||||
@ -167,6 +211,20 @@ size_t Dir::fileSize() {
|
|||||||
return _impl->fileSize();
|
return _impl->fileSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Dir::isFile() const {
|
||||||
|
if (!_impl)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _impl->isFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Dir::isDirectory() const {
|
||||||
|
if (!_impl)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _impl->isDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
bool Dir::next() {
|
bool Dir::next() {
|
||||||
if (!_impl) {
|
if (!_impl) {
|
||||||
return false;
|
return false;
|
||||||
@ -175,6 +233,22 @@ bool Dir::next() {
|
|||||||
return _impl->next();
|
return _impl->next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Dir::rewind() {
|
||||||
|
if (!_impl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _impl->rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FS::setConfig(const FSConfig &cfg) {
|
||||||
|
if (!_impl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _impl->setConfig(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
bool FS::begin() {
|
bool FS::begin() {
|
||||||
if (!_impl) {
|
if (!_impl) {
|
||||||
return false;
|
return false;
|
||||||
@ -217,8 +291,7 @@ File FS::open(const char* path, const char* mode) {
|
|||||||
DEBUGV("FS::open: invalid mode `%s`\r\n", mode);
|
DEBUGV("FS::open: invalid mode `%s`\r\n", mode);
|
||||||
return File();
|
return File();
|
||||||
}
|
}
|
||||||
|
return File(_impl->open(path, om, am), this);
|
||||||
return File(_impl->open(path, om, am));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FS::exists(const char* path) {
|
bool FS::exists(const char* path) {
|
||||||
@ -236,7 +309,8 @@ Dir FS::openDir(const char* path) {
|
|||||||
if (!_impl) {
|
if (!_impl) {
|
||||||
return Dir();
|
return Dir();
|
||||||
}
|
}
|
||||||
return Dir(_impl->openDir(path));
|
DirImplPtr p = _impl->openDir(path);
|
||||||
|
return Dir(p, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Dir FS::openDir(const String& path) {
|
Dir FS::openDir(const String& path) {
|
||||||
@ -254,6 +328,28 @@ bool FS::remove(const String& path) {
|
|||||||
return remove(path.c_str());
|
return remove(path.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FS::rmdir(const char* path) {
|
||||||
|
if (!_impl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _impl->rmdir(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FS::rmdir(const String& path) {
|
||||||
|
return rmdir(path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FS::mkdir(const char* path) {
|
||||||
|
if (!_impl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _impl->mkdir(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FS::mkdir(const String& path) {
|
||||||
|
return mkdir(path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
bool FS::rename(const char* pathFrom, const char* pathTo) {
|
bool FS::rename(const char* pathFrom, const char* pathTo) {
|
||||||
if (!_impl) {
|
if (!_impl) {
|
||||||
return false;
|
return false;
|
||||||
@ -266,6 +362,7 @@ bool FS::rename(const String& pathFrom, const String& pathTo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static bool sflags(const char* mode, OpenMode& om, AccessMode& am) {
|
static bool sflags(const char* mode, OpenMode& om, AccessMode& am) {
|
||||||
switch (mode[0]) {
|
switch (mode[0]) {
|
||||||
case 'r':
|
case 'r':
|
||||||
|
@ -28,6 +28,7 @@ namespace fs {
|
|||||||
|
|
||||||
class File;
|
class File;
|
||||||
class Dir;
|
class Dir;
|
||||||
|
class FS;
|
||||||
|
|
||||||
class FileImpl;
|
class FileImpl;
|
||||||
typedef std::shared_ptr<FileImpl> FileImplPtr;
|
typedef std::shared_ptr<FileImpl> FileImplPtr;
|
||||||
@ -48,7 +49,7 @@ enum SeekMode {
|
|||||||
class File : public Stream
|
class File : public Stream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
File(FileImplPtr p = FileImplPtr()) : _p(p) {}
|
File(FileImplPtr p = FileImplPtr(), FS *baseFS = nullptr) : _p(p), _fakeDir(nullptr), _baseFS(baseFS) { }
|
||||||
|
|
||||||
// Print methods:
|
// Print methods:
|
||||||
size_t write(uint8_t) override;
|
size_t write(uint8_t) override;
|
||||||
@ -72,23 +73,44 @@ public:
|
|||||||
void close();
|
void close();
|
||||||
operator bool() const;
|
operator bool() const;
|
||||||
const char* name() const;
|
const char* name() const;
|
||||||
|
const char* fullName() const; // Includes path
|
||||||
|
bool truncate(uint32_t size);
|
||||||
|
|
||||||
|
bool isFile() const;
|
||||||
|
bool isDirectory() const;
|
||||||
|
|
||||||
|
// Arduino "class SD" methods for compatibility
|
||||||
|
size_t write(const char *str) { return write((const uint8_t*)str, strlen(str)); }
|
||||||
|
void rewindDirectory();
|
||||||
|
File openNextFile();
|
||||||
|
|
||||||
String readString() override;
|
String readString() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
FileImplPtr _p;
|
FileImplPtr _p;
|
||||||
|
|
||||||
|
// Arduino SD class emulation
|
||||||
|
std::shared_ptr<Dir> _fakeDir;
|
||||||
|
FS *_baseFS;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Dir {
|
class Dir {
|
||||||
public:
|
public:
|
||||||
Dir(DirImplPtr impl = DirImplPtr()): _impl(impl) { }
|
Dir(DirImplPtr impl = DirImplPtr(), FS *baseFS = nullptr): _impl(impl), _baseFS(baseFS) { }
|
||||||
|
|
||||||
File openFile(const char* mode);
|
File openFile(const char* mode);
|
||||||
|
|
||||||
String fileName();
|
String fileName();
|
||||||
size_t fileSize();
|
size_t fileSize();
|
||||||
|
bool isFile() const;
|
||||||
|
bool isDirectory() const;
|
||||||
|
|
||||||
bool next();
|
bool next();
|
||||||
|
bool rewind();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DirImplPtr _impl;
|
DirImplPtr _impl;
|
||||||
|
FS *_baseFS;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FSInfo {
|
struct FSInfo {
|
||||||
@ -100,11 +122,41 @@ struct FSInfo {
|
|||||||
size_t maxPathLength;
|
size_t maxPathLength;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class FSConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FSConfig(bool autoFormat = true) {
|
||||||
|
_type = FSConfig::fsid::FSId;
|
||||||
|
_autoFormat = autoFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum fsid { FSId = 0x00000000 };
|
||||||
|
FSConfig setAutoFormat(bool val = true) {
|
||||||
|
_autoFormat = val;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t _type;
|
||||||
|
bool _autoFormat;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SPIFFSConfig : public FSConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SPIFFSConfig(bool autoFormat = true) {
|
||||||
|
_type = SPIFFSConfig::fsid::FSId;
|
||||||
|
_autoFormat = autoFormat;
|
||||||
|
}
|
||||||
|
enum fsid { FSId = 0x53504946 };
|
||||||
|
};
|
||||||
|
|
||||||
class FS
|
class FS
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FS(FSImplPtr impl) : _impl(impl) { }
|
FS(FSImplPtr impl) : _impl(impl) { }
|
||||||
|
|
||||||
|
bool setConfig(const FSConfig &cfg);
|
||||||
|
|
||||||
bool begin();
|
bool begin();
|
||||||
void end();
|
void end();
|
||||||
|
|
||||||
@ -126,6 +178,12 @@ public:
|
|||||||
bool rename(const char* pathFrom, const char* pathTo);
|
bool rename(const char* pathFrom, const char* pathTo);
|
||||||
bool rename(const String& pathFrom, const String& pathTo);
|
bool rename(const String& pathFrom, const String& pathTo);
|
||||||
|
|
||||||
|
bool mkdir(const char* path);
|
||||||
|
bool mkdir(const String& path);
|
||||||
|
|
||||||
|
bool rmdir(const char* path);
|
||||||
|
bool rmdir(const String& path);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
FSImplPtr _impl;
|
FSImplPtr _impl;
|
||||||
};
|
};
|
||||||
@ -141,6 +199,7 @@ using fs::SeekSet;
|
|||||||
using fs::SeekCur;
|
using fs::SeekCur;
|
||||||
using fs::SeekEnd;
|
using fs::SeekEnd;
|
||||||
using fs::FSInfo;
|
using fs::FSInfo;
|
||||||
|
using fs::FSConfig;
|
||||||
#endif //FS_NO_GLOBALS
|
#endif //FS_NO_GLOBALS
|
||||||
|
|
||||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SPIFFS)
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SPIFFS)
|
||||||
|
@ -34,8 +34,12 @@ public:
|
|||||||
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
|
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
|
||||||
virtual size_t position() const = 0;
|
virtual size_t position() const = 0;
|
||||||
virtual size_t size() const = 0;
|
virtual size_t size() const = 0;
|
||||||
|
virtual bool truncate(uint32_t size) = 0;
|
||||||
virtual void close() = 0;
|
virtual void close() = 0;
|
||||||
virtual const char* name() const = 0;
|
virtual const char* name() const = 0;
|
||||||
|
virtual const char* fullName() const = 0;
|
||||||
|
virtual bool isFile() const = 0;
|
||||||
|
virtual bool isDirectory() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum OpenMode {
|
enum OpenMode {
|
||||||
@ -57,12 +61,16 @@ public:
|
|||||||
virtual FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) = 0;
|
virtual FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) = 0;
|
||||||
virtual const char* fileName() = 0;
|
virtual const char* fileName() = 0;
|
||||||
virtual size_t fileSize() = 0;
|
virtual size_t fileSize() = 0;
|
||||||
|
virtual bool isFile() const = 0;
|
||||||
|
virtual bool isDirectory() const = 0;
|
||||||
virtual bool next() = 0;
|
virtual bool next() = 0;
|
||||||
|
virtual bool rewind() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class FSImpl {
|
class FSImpl {
|
||||||
public:
|
public:
|
||||||
virtual ~FSImpl () { }
|
virtual ~FSImpl () { }
|
||||||
|
virtual bool setConfig(const FSConfig &cfg) = 0;
|
||||||
virtual bool begin() = 0;
|
virtual bool begin() = 0;
|
||||||
virtual void end() = 0;
|
virtual void end() = 0;
|
||||||
virtual bool format() = 0;
|
virtual bool format() = 0;
|
||||||
@ -72,7 +80,8 @@ public:
|
|||||||
virtual DirImplPtr openDir(const char* path) = 0;
|
virtual DirImplPtr openDir(const char* path) = 0;
|
||||||
virtual bool rename(const char* pathFrom, const char* pathTo) = 0;
|
virtual bool rename(const char* pathFrom, const char* pathTo) = 0;
|
||||||
virtual bool remove(const char* path) = 0;
|
virtual bool remove(const char* path) = 0;
|
||||||
|
virtual bool mkdir(const char* path) = 0;
|
||||||
|
virtual bool rmdir(const char* path) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace fs
|
} // namespace fs
|
||||||
|
@ -29,7 +29,10 @@
|
|||||||
#undef max
|
#undef max
|
||||||
#undef min
|
#undef min
|
||||||
#include "FSImpl.h"
|
#include "FSImpl.h"
|
||||||
|
extern "C" {
|
||||||
#include "spiffs/spiffs.h"
|
#include "spiffs/spiffs.h"
|
||||||
|
#include "spiffs/spiffs_nucleus.h"
|
||||||
|
};
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "flash_utils.h"
|
#include "flash_utils.h"
|
||||||
|
|
||||||
@ -124,6 +127,27 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool mkdir(const char* path) override
|
||||||
|
{
|
||||||
|
(void)path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rmdir(const char* path) override
|
||||||
|
{
|
||||||
|
(void)path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setConfig(const FSConfig &cfg) override
|
||||||
|
{
|
||||||
|
if ((cfg._type != SPIFFSConfig::fsid::FSId) || (SPIFFS_mounted(&_fs) != 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_cfg = *static_cast<const SPIFFSConfig *>(&cfg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool begin() override
|
bool begin() override
|
||||||
{
|
{
|
||||||
if (SPIFFS_mounted(&_fs) != 0) {
|
if (SPIFFS_mounted(&_fs) != 0) {
|
||||||
@ -136,12 +160,16 @@ public:
|
|||||||
if (_tryMount()) {
|
if (_tryMount()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (_cfg._autoFormat) {
|
||||||
auto rc = SPIFFS_format(&_fs);
|
auto rc = SPIFFS_format(&_fs);
|
||||||
if (rc != SPIFFS_OK) {
|
if (rc != SPIFFS_OK) {
|
||||||
DEBUGV("SPIFFS_format: rc=%d, err=%d\r\n", rc, _fs.err_code);
|
DEBUGV("SPIFFS_format: rc=%d, err=%d\r\n", rc, _fs.err_code);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return _tryMount();
|
return _tryMount();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void end() override
|
void end() override
|
||||||
@ -279,6 +307,8 @@ protected:
|
|||||||
std::unique_ptr<uint8_t[]> _workBuf;
|
std::unique_ptr<uint8_t[]> _workBuf;
|
||||||
std::unique_ptr<uint8_t[]> _fdsBuf;
|
std::unique_ptr<uint8_t[]> _fdsBuf;
|
||||||
std::unique_ptr<uint8_t[]> _cacheBuf;
|
std::unique_ptr<uint8_t[]> _cacheBuf;
|
||||||
|
|
||||||
|
SPIFFSConfig _cfg;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define CHECKFD() while (_fd == 0) { panic(); }
|
#define CHECKFD() while (_fd == 0) { panic(); }
|
||||||
@ -375,6 +405,29 @@ public:
|
|||||||
return _stat.size;
|
return _stat.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool truncate(uint32_t size) override
|
||||||
|
{
|
||||||
|
CHECKFD();
|
||||||
|
spiffs_fd *sfd;
|
||||||
|
if (spiffs_fd_get(_fs->getFs(), _fd, &sfd) == SPIFFS_OK) {
|
||||||
|
return SPIFFS_OK == spiffs_object_truncate(sfd, size, 0);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFile() const override
|
||||||
|
{
|
||||||
|
// No such thing as directories on SPIFFS
|
||||||
|
return _fd ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDirectory() const override
|
||||||
|
{
|
||||||
|
// No such thing as directories on SPIFFS
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void close() override
|
void close() override
|
||||||
{
|
{
|
||||||
CHECKFD();
|
CHECKFD();
|
||||||
@ -390,6 +443,11 @@ public:
|
|||||||
return (const char*) _stat.name;
|
return (const char*) _stat.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* fullName() const override
|
||||||
|
{
|
||||||
|
return name(); // No dirs, they're the same on SPIFFS
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void _getStat() const
|
void _getStat() const
|
||||||
{
|
{
|
||||||
@ -415,6 +473,7 @@ public:
|
|||||||
: _pattern(pattern)
|
: _pattern(pattern)
|
||||||
, _fs(fs)
|
, _fs(fs)
|
||||||
, _dir(dir)
|
, _dir(dir)
|
||||||
|
, _dirHead(dir)
|
||||||
, _valid(false)
|
, _valid(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -458,6 +517,18 @@ public:
|
|||||||
return _dirent.size;
|
return _dirent.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isFile() const override
|
||||||
|
{
|
||||||
|
// No such thing as directories on SPIFFS
|
||||||
|
return _valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDirectory() const override
|
||||||
|
{
|
||||||
|
// No such thing as directories on SPIFFS
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool next() override
|
bool next() override
|
||||||
{
|
{
|
||||||
const int n = _pattern.length();
|
const int n = _pattern.length();
|
||||||
@ -468,13 +539,20 @@ public:
|
|||||||
return _valid;
|
return _valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool rewind() override
|
||||||
|
{
|
||||||
|
_dir = _dirHead;
|
||||||
|
_valid = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
String _pattern;
|
String _pattern;
|
||||||
SPIFFSImpl* _fs;
|
SPIFFSImpl* _fs;
|
||||||
spiffs_DIR _dir;
|
spiffs_DIR _dir;
|
||||||
|
spiffs_DIR _dirHead; // The pointer to the start of this dir
|
||||||
spiffs_dirent _dirent;
|
spiffs_dirent _dirent;
|
||||||
bool _valid;
|
bool _valid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif//spiffs_api_h
|
#endif//spiffs_api_h
|
||||||
|
@ -125,6 +125,24 @@ directory into ESP8266 flash file system.
|
|||||||
File system object (SPIFFS)
|
File system object (SPIFFS)
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
setConfig
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
.. code:: cpp
|
||||||
|
|
||||||
|
SPIFFSConfig cfg;
|
||||||
|
cfg.setAutoFormat(false);
|
||||||
|
SPIFFS.setConfig(cfg);
|
||||||
|
|
||||||
|
This method allows you to configure the parameters of a filesystem
|
||||||
|
before mounting. All filesystems have their own ``*Config`` (i.e.
|
||||||
|
``SDFSConfig`` or ``SPIFFSConfig`` with their custom set of options.
|
||||||
|
All filesystems allow explicitly enabling/disabling formatting when
|
||||||
|
mounts fail. If you do not call this ``setConfig`` method before
|
||||||
|
perforing ``begin()``, you will get the filesystem's default
|
||||||
|
behavior and configuration. By default, SPIFFS will autoformat the
|
||||||
|
filesystem if it cannot mount it, while SDFS will not.
|
||||||
|
|
||||||
begin
|
begin
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
@ -134,7 +152,8 @@ begin
|
|||||||
|
|
||||||
This method mounts SPIFFS file system. It must be called before any
|
This method mounts SPIFFS file system. It must be called before any
|
||||||
other FS APIs are used. Returns *true* if file system was mounted
|
other FS APIs are used. Returns *true* if file system was mounted
|
||||||
successfully, false otherwise.
|
successfully, false otherwise. With no options it will format SPIFFS
|
||||||
|
if it is unable to mount it on the first try.
|
||||||
|
|
||||||
end
|
end
|
||||||
~~~
|
~~~
|
||||||
|
1
libraries/ESP8266SdFat
Submodule
1
libraries/ESP8266SdFat
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 92ab4b59eb920bf3134347dbe07e7c059eb64d46
|
@ -1,24 +0,0 @@
|
|||||||
= SD Library for Arduino =
|
|
||||||
|
|
||||||
The SD library allows for reading from and writing to SD cards.
|
|
||||||
|
|
||||||
For more information about this library please visit us at
|
|
||||||
http://www.arduino.cc/en/Reference/SD
|
|
||||||
|
|
||||||
== License ==
|
|
||||||
|
|
||||||
Copyright (C) 2009 by William Greiman
|
|
||||||
Copyright (c) 2010 SparkFun Electronics
|
|
||||||
|
|
||||||
This program 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 program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
9
libraries/SD/README.md
Normal file
9
libraries/SD/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Arduino "class SD" shim wrapper
|
||||||
|
|
||||||
|
This is a simple wrapper class to replace the ancient Arduino SD.h
|
||||||
|
access method for SD cards. It calls the underlying SDFS and the latest
|
||||||
|
SdFat lib to do all the work, and is now compatible with the rest of the
|
||||||
|
ESP8266 filesystem things.
|
||||||
|
|
||||||
|
-Earle F. Philhower, III
|
||||||
|
<earlephilhower@yahoo.com>
|
@ -1,111 +0,0 @@
|
|||||||
/*
|
|
||||||
SD card test
|
|
||||||
|
|
||||||
This example shows how use the utility libraries on which the'
|
|
||||||
SD library is based in order to get info about your SD card.
|
|
||||||
Very useful for testing a card when you're not sure whether its working or not.
|
|
||||||
|
|
||||||
The circuit:
|
|
||||||
SD card attached to SPI bus as follows:
|
|
||||||
** MOSI - pin 11 on Arduino Uno/Duemilanove/Diecimila
|
|
||||||
** MISO - pin 12 on Arduino Uno/Duemilanove/Diecimila
|
|
||||||
** CLK - pin 13 on Arduino Uno/Duemilanove/Diecimila
|
|
||||||
** CS - depends on your SD card shield or module.
|
|
||||||
Pin 4 used here for consistency with other Arduino examples
|
|
||||||
|
|
||||||
|
|
||||||
created 28 Mar 2011
|
|
||||||
by Limor Fried
|
|
||||||
modified 9 Apr 2012
|
|
||||||
by Tom Igoe
|
|
||||||
*/
|
|
||||||
// include the SD library:
|
|
||||||
#include <SPI.h>
|
|
||||||
#include <SD.h>
|
|
||||||
|
|
||||||
// set up variables using the SD utility library functions:
|
|
||||||
Sd2Card card;
|
|
||||||
SdVolume volume;
|
|
||||||
SdFile root;
|
|
||||||
|
|
||||||
// change this to match your SD shield or module;
|
|
||||||
// Arduino Ethernet shield: pin 4
|
|
||||||
// Adafruit SD shields and modules: pin 10
|
|
||||||
// Sparkfun SD shield: pin 8
|
|
||||||
const int chipSelect = 4;
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
// Open serial communications and wait for port to open:
|
|
||||||
Serial.begin(9600);
|
|
||||||
while (!Serial) {
|
|
||||||
; // wait for serial port to connect. Needed for Leonardo only
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Serial.print("\nInitializing SD card...");
|
|
||||||
|
|
||||||
// we'll use the initialization code from the utility libraries
|
|
||||||
// since we're just testing if the card is working!
|
|
||||||
if (!card.init(SPI_HALF_SPEED, chipSelect)) {
|
|
||||||
Serial.println("initialization failed. Things to check:");
|
|
||||||
Serial.println("* is a card inserted?");
|
|
||||||
Serial.println("* is your wiring correct?");
|
|
||||||
Serial.println("* did you change the chipSelect pin to match your shield or module?");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
Serial.println("Wiring is correct and a card is present.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// print the type of card
|
|
||||||
Serial.print("\nCard type: ");
|
|
||||||
switch (card.type()) {
|
|
||||||
case SD_CARD_TYPE_SD1:
|
|
||||||
Serial.println("SD1");
|
|
||||||
break;
|
|
||||||
case SD_CARD_TYPE_SD2:
|
|
||||||
Serial.println("SD2");
|
|
||||||
break;
|
|
||||||
case SD_CARD_TYPE_SDHC:
|
|
||||||
Serial.println("SDHC");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Serial.println("Unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
|
|
||||||
if (!volume.init(card)) {
|
|
||||||
Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// print the type and size of the first FAT-type volume
|
|
||||||
uint32_t volumesize;
|
|
||||||
Serial.print("\nVolume type is FAT");
|
|
||||||
Serial.println(volume.fatType(), DEC);
|
|
||||||
Serial.println();
|
|
||||||
|
|
||||||
volumesize = volume.blocksPerCluster(); // clusters are collections of blocks
|
|
||||||
volumesize *= volume.clusterCount(); // we'll have a lot of clusters
|
|
||||||
volumesize *= 512; // SD card blocks are always 512 bytes
|
|
||||||
Serial.print("Volume size (bytes): ");
|
|
||||||
Serial.println(volumesize);
|
|
||||||
Serial.print("Volume size (Kbytes): ");
|
|
||||||
volumesize /= 1024;
|
|
||||||
Serial.println(volumesize);
|
|
||||||
Serial.print("Volume size (Mbytes): ");
|
|
||||||
volumesize /= 1024;
|
|
||||||
Serial.println(volumesize);
|
|
||||||
|
|
||||||
|
|
||||||
Serial.println("\nFiles found on the card (name, date and size in bytes): ");
|
|
||||||
root.openRoot(volume);
|
|
||||||
|
|
||||||
// list all files in the card with date and size
|
|
||||||
root.ls(LS_R | LS_DATE | LS_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void loop(void) {
|
|
||||||
|
|
||||||
}
|
|
@ -1,10 +1,10 @@
|
|||||||
name=SD(esp8266)
|
name=SD(esp8266)
|
||||||
version=1.0.5
|
version=2.0.0
|
||||||
author=Arduino, SparkFun
|
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||||
maintainer=Arduino <info@arduino.cc>
|
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||||
sentence=Enables reading and writing on SD cards. For all Arduino boards.
|
sentence=Enables reading and writing on SD cards using SDFS on the ESP8266.
|
||||||
paragraph=Once an SD memory card is connected to the SPI interfare of the Arduino board you are enabled to create files and read/write on them. You can also move through directories on the SD card.
|
paragraph=Once an SD memory card is connected to the SPI interfare of the Arduino board you are enabled to create files and read/write on them. You can also move through directories on the SD card. This is a complete rewrite of the original Arduino SD.h class.
|
||||||
category=Data Storage
|
category=Data Storage
|
||||||
url=http://www.arduino.cc/en/Reference/SD
|
url=http://www.github.com/esp8266/Arduino
|
||||||
architectures=esp8266
|
architectures=esp8266
|
||||||
dot_a_linkage=true
|
dot_a_linkage=true
|
||||||
|
@ -1,149 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
SD - a slightly more friendly wrapper for sdfatlib
|
|
||||||
|
|
||||||
This library aims to expose a subset of SD card functionality
|
|
||||||
in the form of a higher level "wrapper" object.
|
|
||||||
|
|
||||||
License: GNU General Public License V3
|
|
||||||
(Because sdfatlib is licensed with this.)
|
|
||||||
|
|
||||||
(C) Copyright 2010 SparkFun Electronics
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <SD.h>
|
|
||||||
|
|
||||||
/* for debugging file open/close leaks
|
|
||||||
uint8_t nfilecount=0;
|
|
||||||
*/
|
|
||||||
|
|
||||||
File::File(SdFile f, const char *n) {
|
|
||||||
// oh man you are kidding me, new() doesnt exist? Ok we do it by hand!
|
|
||||||
_file = (SdFile *)malloc(sizeof(SdFile));
|
|
||||||
if (_file) {
|
|
||||||
memcpy(_file, &f, sizeof(SdFile));
|
|
||||||
|
|
||||||
strncpy(_name, n, 12);
|
|
||||||
_name[12] = 0;
|
|
||||||
|
|
||||||
/* for debugging file open/close leaks
|
|
||||||
nfilecount++;
|
|
||||||
Serial.print("Created \"");
|
|
||||||
Serial.print(n);
|
|
||||||
Serial.print("\": ");
|
|
||||||
Serial.println(nfilecount, DEC);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File::File(void) {
|
|
||||||
_file = 0;
|
|
||||||
_name[0] = 0;
|
|
||||||
//Serial.print("Created empty file object");
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns a pointer to the file name
|
|
||||||
char *File::name(void) {
|
|
||||||
return _name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a directory is a special type of file
|
|
||||||
boolean File::isDirectory(void) {
|
|
||||||
return (_file && _file->isDir());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
size_t File::write(uint8_t val) {
|
|
||||||
return write(&val, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t File::write(const uint8_t *buf, size_t size) {
|
|
||||||
size_t t;
|
|
||||||
if (!_file) {
|
|
||||||
setWriteError();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
_file->clearWriteError();
|
|
||||||
t = _file->write(buf, size);
|
|
||||||
if (_file->getWriteError()) {
|
|
||||||
setWriteError();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
int File::peek() {
|
|
||||||
if (! _file)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
int c = _file->read();
|
|
||||||
if (c != -1) _file->seekCur(-1);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
int File::read() {
|
|
||||||
if (_file)
|
|
||||||
return _file->read();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// buffered read for more efficient, high speed reading
|
|
||||||
int File::read(void *buf, uint16_t nbyte) {
|
|
||||||
if (_file)
|
|
||||||
return _file->read(buf, nbyte);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t File::readBytes(char *buffer, size_t length) {
|
|
||||||
int result = read(buffer, (uint16_t)length);
|
|
||||||
return result < 0 ? 0 : (size_t)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int File::available() {
|
|
||||||
if (! _file) return 0;
|
|
||||||
|
|
||||||
return size() - position();
|
|
||||||
}
|
|
||||||
|
|
||||||
void File::flush() {
|
|
||||||
if (_file)
|
|
||||||
_file->sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean File::seek(uint32_t pos) {
|
|
||||||
if (! _file) return false;
|
|
||||||
|
|
||||||
return _file->seekSet(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t File::position() {
|
|
||||||
if (! _file) return -1;
|
|
||||||
return _file->curPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t File::size() {
|
|
||||||
if (! _file) return 0;
|
|
||||||
return _file->fileSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
void File::close() {
|
|
||||||
if (_file) {
|
|
||||||
_file->close();
|
|
||||||
free(_file);
|
|
||||||
_file = 0;
|
|
||||||
|
|
||||||
/* for debugging file open/close leaks
|
|
||||||
nfilecount--;
|
|
||||||
Serial.print("Deleted ");
|
|
||||||
Serial.println(nfilecount, DEC);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File::operator bool() {
|
|
||||||
if (_file)
|
|
||||||
return _file->isOpen();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
|||||||
|
|
||||||
** SD - a slightly more friendly wrapper for sdfatlib **
|
|
||||||
|
|
||||||
This library aims to expose a subset of SD card functionality in the
|
|
||||||
form of a higher level "wrapper" object.
|
|
||||||
|
|
||||||
License: GNU General Public License V3
|
|
||||||
(Because sdfatlib is licensed with this.)
|
|
||||||
|
|
||||||
(C) Copyright 2010 SparkFun Electronics
|
|
||||||
|
|
||||||
Now better than ever with optimization, multiple file support, directory handling, etc - ladyada!
|
|
||||||
|
|
@ -1,632 +1,5 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
SD - a slightly more friendly wrapper for sdfatlib
|
|
||||||
|
|
||||||
This library aims to expose a subset of SD card functionality
|
|
||||||
in the form of a higher level "wrapper" object.
|
|
||||||
|
|
||||||
License: GNU General Public License V3
|
|
||||||
(Because sdfatlib is licensed with this.)
|
|
||||||
|
|
||||||
(C) Copyright 2010 SparkFun Electronics
|
|
||||||
|
|
||||||
|
|
||||||
This library provides four key benefits:
|
|
||||||
|
|
||||||
* Including `SD.h` automatically creates a global
|
|
||||||
`SD` object which can be interacted with in a similar
|
|
||||||
manner to other standard global objects like `Serial` and `Ethernet`.
|
|
||||||
|
|
||||||
* Boilerplate initialisation code is contained in one method named
|
|
||||||
`begin` and no further objects need to be created in order to access
|
|
||||||
the SD card.
|
|
||||||
|
|
||||||
* Calls to `open` can supply a full path name including parent
|
|
||||||
directories which simplifies interacting with files in subdirectories.
|
|
||||||
|
|
||||||
* Utility methods are provided to determine whether a file exists
|
|
||||||
and to create a directory heirarchy.
|
|
||||||
|
|
||||||
|
|
||||||
Note however that not all functionality provided by the underlying
|
|
||||||
sdfatlib library is exposed.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Implementation Notes
|
|
||||||
|
|
||||||
In order to handle multi-directory path traversal, functionality that
|
|
||||||
requires this ability is implemented as callback functions.
|
|
||||||
|
|
||||||
Individual methods call the `walkPath` function which performs the actual
|
|
||||||
directory traversal (swapping between two different directory/file handles
|
|
||||||
along the way) and at each level calls the supplied callback function.
|
|
||||||
|
|
||||||
Some types of functionality will take an action at each level (e.g. exists
|
|
||||||
or make directory) which others will only take an action at the bottom
|
|
||||||
level (e.g. open).
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "SD.h"
|
#include "SD.h"
|
||||||
|
|
||||||
// Used by `getNextPathComponent`
|
|
||||||
#define MAX_COMPONENT_LEN 12 // What is max length?
|
|
||||||
#define PATH_COMPONENT_BUFFER_LEN MAX_COMPONENT_LEN+1
|
|
||||||
|
|
||||||
bool getNextPathComponent(const char *path, unsigned int *p_offset,
|
|
||||||
char *buffer) {
|
|
||||||
/*
|
|
||||||
|
|
||||||
Parse individual path components from a path.
|
|
||||||
|
|
||||||
e.g. after repeated calls '/foo/bar/baz' will be split
|
|
||||||
into 'foo', 'bar', 'baz'.
|
|
||||||
|
|
||||||
This is similar to `strtok()` but copies the component into the
|
|
||||||
supplied buffer rather than modifying the original string.
|
|
||||||
|
|
||||||
|
|
||||||
`buffer` needs to be PATH_COMPONENT_BUFFER_LEN in size.
|
|
||||||
|
|
||||||
`p_offset` needs to point to an integer of the offset at
|
|
||||||
which the previous path component finished.
|
|
||||||
|
|
||||||
Returns `true` if more components remain.
|
|
||||||
|
|
||||||
Returns `false` if this is the last component.
|
|
||||||
(This means path ended with 'foo' or 'foo/'.)
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO: Have buffer local to this function, so we know it's the
|
|
||||||
// correct length?
|
|
||||||
|
|
||||||
int bufferOffset = 0;
|
|
||||||
|
|
||||||
int offset = *p_offset;
|
|
||||||
|
|
||||||
// Skip root or other separator
|
|
||||||
if (path[offset] == '/') {
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the next next path segment
|
|
||||||
while (bufferOffset < MAX_COMPONENT_LEN
|
|
||||||
&& (path[offset] != '/')
|
|
||||||
&& (path[offset] != '\0')) {
|
|
||||||
buffer[bufferOffset++] = path[offset++];
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer[bufferOffset] = '\0';
|
|
||||||
|
|
||||||
// Skip trailing separator so we can determine if this
|
|
||||||
// is the last component in the path or not.
|
|
||||||
if (path[offset] == '/') {
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
|
|
||||||
*p_offset = offset;
|
|
||||||
|
|
||||||
return (path[offset] != '\0');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
boolean walkPath(const char *filepath, SdFile& parentDir,
|
|
||||||
boolean (*callback)(SdFile& parentDir,
|
|
||||||
char *filePathComponent,
|
|
||||||
boolean isLastComponent,
|
|
||||||
void *object),
|
|
||||||
void *object = NULL) {
|
|
||||||
/*
|
|
||||||
|
|
||||||
When given a file path (and parent directory--normally root),
|
|
||||||
this function traverses the directories in the path and at each
|
|
||||||
level calls the supplied callback function while also providing
|
|
||||||
the supplied object for context if required.
|
|
||||||
|
|
||||||
e.g. given the path '/foo/bar/baz'
|
|
||||||
the callback would be called at the equivalent of
|
|
||||||
'/foo', '/foo/bar' and '/foo/bar/baz'.
|
|
||||||
|
|
||||||
The implementation swaps between two different directory/file
|
|
||||||
handles as it traverses the directories and does not use recursion
|
|
||||||
in an attempt to use memory efficiently.
|
|
||||||
|
|
||||||
If a callback wishes to stop the directory traversal it should
|
|
||||||
return false--in this case the function will stop the traversal,
|
|
||||||
tidy up and return false.
|
|
||||||
|
|
||||||
If a directory path doesn't exist at some point this function will
|
|
||||||
also return false and not subsequently call the callback.
|
|
||||||
|
|
||||||
If a directory path specified is complete, valid and the callback
|
|
||||||
did not indicate the traversal should be interrupted then this
|
|
||||||
function will return true.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
SdFile subfile1;
|
|
||||||
SdFile subfile2;
|
|
||||||
|
|
||||||
char buffer[PATH_COMPONENT_BUFFER_LEN];
|
|
||||||
|
|
||||||
unsigned int offset = 0;
|
|
||||||
|
|
||||||
SdFile *p_parent;
|
|
||||||
SdFile *p_child;
|
|
||||||
|
|
||||||
SdFile *p_tmp_sdfile;
|
|
||||||
|
|
||||||
p_child = &subfile1;
|
|
||||||
|
|
||||||
p_parent = &parentDir;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
|
|
||||||
boolean moreComponents = getNextPathComponent(filepath, &offset, buffer);
|
|
||||||
|
|
||||||
boolean shouldContinue = callback((*p_parent), buffer, !moreComponents, object);
|
|
||||||
|
|
||||||
if (!shouldContinue) {
|
|
||||||
// TODO: Don't repeat this code?
|
|
||||||
// If it's one we've created then we
|
|
||||||
// don't need the parent handle anymore.
|
|
||||||
if (p_parent != &parentDir) {
|
|
||||||
(*p_parent).close();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!moreComponents) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean exists = (*p_child).open(*p_parent, buffer, O_RDONLY);
|
|
||||||
|
|
||||||
// If it's one we've created then we
|
|
||||||
// don't need the parent handle anymore.
|
|
||||||
if (p_parent != &parentDir) {
|
|
||||||
(*p_parent).close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle case when it doesn't exist and we can't continue...
|
|
||||||
if (exists) {
|
|
||||||
// We alternate between two file handles as we go down
|
|
||||||
// the path.
|
|
||||||
if (p_parent == &parentDir) {
|
|
||||||
p_parent = &subfile2;
|
|
||||||
}
|
|
||||||
|
|
||||||
p_tmp_sdfile = p_parent;
|
|
||||||
p_parent = p_child;
|
|
||||||
p_child = p_tmp_sdfile;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p_parent != &parentDir) {
|
|
||||||
(*p_parent).close(); // TODO: Return/ handle different?
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
The callbacks used to implement various functionality follow.
|
|
||||||
|
|
||||||
Each callback is supplied with a parent directory handle,
|
|
||||||
character string with the name of the current file path component,
|
|
||||||
a flag indicating if this component is the last in the path and
|
|
||||||
a pointer to an arbitrary object used for context.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
boolean callback_pathExists(SdFile& parentDir, char *filePathComponent,
|
|
||||||
boolean isLastComponent, void *object) {
|
|
||||||
/*
|
|
||||||
|
|
||||||
Callback used to determine if a file/directory exists in parent
|
|
||||||
directory.
|
|
||||||
|
|
||||||
Returns true if file path exists.
|
|
||||||
|
|
||||||
*/
|
|
||||||
SdFile child;
|
|
||||||
(void) isLastComponent;
|
|
||||||
(void) object;
|
|
||||||
|
|
||||||
boolean exists = child.open(parentDir, filePathComponent, O_RDONLY);
|
|
||||||
|
|
||||||
if (exists) {
|
|
||||||
child.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return exists;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
boolean callback_makeDirPath(SdFile& parentDir, char *filePathComponent,
|
|
||||||
boolean isLastComponent, void *object) {
|
|
||||||
/*
|
|
||||||
|
|
||||||
Callback used to create a directory in the parent directory if
|
|
||||||
it does not already exist.
|
|
||||||
|
|
||||||
Returns true if a directory was created or it already existed.
|
|
||||||
|
|
||||||
*/
|
|
||||||
boolean result = false;
|
|
||||||
SdFile child;
|
|
||||||
|
|
||||||
result = callback_pathExists(parentDir, filePathComponent, isLastComponent, object);
|
|
||||||
if (!result) {
|
|
||||||
result = child.makeDir(parentDir, filePathComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
boolean callback_openPath(SdFile& parentDir, char *filePathComponent,
|
|
||||||
boolean isLastComponent, void *object) {
|
|
||||||
|
|
||||||
Callback used to open a file specified by a filepath that may
|
|
||||||
specify one or more directories above it.
|
|
||||||
|
|
||||||
Expects the context object to be an instance of `SDClass` and
|
|
||||||
will use the `file` property of the instance to open the requested
|
|
||||||
file/directory with the associated file open mode property.
|
|
||||||
|
|
||||||
Always returns true if the directory traversal hasn't reached the
|
|
||||||
bottom of the directory heirarchy.
|
|
||||||
|
|
||||||
Returns false once the file has been opened--to prevent the traversal
|
|
||||||
from descending further. (This may be unnecessary.)
|
|
||||||
|
|
||||||
if (isLastComponent) {
|
|
||||||
SDClass *p_SD = static_cast<SDClass*>(object);
|
|
||||||
p_SD->file.open(parentDir, filePathComponent, p_SD->fileOpenMode);
|
|
||||||
if (p_SD->fileOpenMode == FILE_WRITE) {
|
|
||||||
p_SD->file.seekSet(p_SD->file.fileSize());
|
|
||||||
}
|
|
||||||
// TODO: Return file open result?
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
boolean callback_remove(SdFile& parentDir, char *filePathComponent,
|
|
||||||
boolean isLastComponent, void *object) {
|
|
||||||
(void) object;
|
|
||||||
|
|
||||||
if (isLastComponent) {
|
|
||||||
return SdFile::remove(parentDir, filePathComponent);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean callback_rmdir(SdFile& parentDir, char *filePathComponent,
|
|
||||||
boolean isLastComponent, void *object) {
|
|
||||||
(void) object;
|
|
||||||
if (isLastComponent) {
|
|
||||||
SdFile f;
|
|
||||||
if (!f.open(parentDir, filePathComponent, O_READ)) return false;
|
|
||||||
return f.rmDir();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Implementation of class used to create `SDCard` object. */
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
boolean SDClass::begin(uint8_t csPin, uint32_t speed) {
|
|
||||||
/*
|
|
||||||
|
|
||||||
Performs the initialisation required by the sdfatlib library.
|
|
||||||
|
|
||||||
Return true if initialization succeeds, false otherwise.
|
|
||||||
|
|
||||||
*/
|
|
||||||
return card.init(speed, csPin) &&
|
|
||||||
volume.init(card) &&
|
|
||||||
root.openRoot(volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Warning: see comment in SD.h about possible card corruption.
|
|
||||||
void SDClass::end(bool endSPI)
|
|
||||||
{
|
|
||||||
if(card.errorCode() == 0 && root.isOpen()) {
|
|
||||||
root.close(); //Warning: this calls sync(), see above comment about corruption.
|
|
||||||
}
|
|
||||||
|
|
||||||
card.end(endSPI);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// this little helper is used to traverse paths
|
|
||||||
SdFile SDClass::getParentDir(const char *filepath, int *index) {
|
|
||||||
// get parent directory
|
|
||||||
SdFile d1 = root; // start with the mostparent, root!
|
|
||||||
SdFile d2;
|
|
||||||
|
|
||||||
// we'll use the pointers to swap between the two objects
|
|
||||||
SdFile *parent = &d1;
|
|
||||||
SdFile *subdir = &d2;
|
|
||||||
|
|
||||||
const char *origpath = filepath;
|
|
||||||
|
|
||||||
while (strchr(filepath, '/')) {
|
|
||||||
|
|
||||||
// get rid of leading /'s
|
|
||||||
if (filepath[0] == '/') {
|
|
||||||
filepath++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! strchr(filepath, '/')) {
|
|
||||||
// it was in the root directory, so leave now
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract just the name of the next subdirectory
|
|
||||||
uint8_t idx = strchr(filepath, '/') - filepath;
|
|
||||||
if (idx > 12)
|
|
||||||
idx = 12; // dont let them specify long names
|
|
||||||
char subdirname[13];
|
|
||||||
strncpy(subdirname, filepath, idx);
|
|
||||||
subdirname[idx] = 0;
|
|
||||||
|
|
||||||
// close the subdir (we reuse them) if open
|
|
||||||
subdir->close();
|
|
||||||
if (! subdir->open(parent, subdirname, O_READ)) {
|
|
||||||
// failed to open one of the subdirectories
|
|
||||||
return SdFile();
|
|
||||||
}
|
|
||||||
// move forward to the next subdirectory
|
|
||||||
filepath += idx;
|
|
||||||
|
|
||||||
// we reuse the objects, close it.
|
|
||||||
parent->close();
|
|
||||||
|
|
||||||
// swap the pointers
|
|
||||||
SdFile *t = parent;
|
|
||||||
parent = subdir;
|
|
||||||
subdir = t;
|
|
||||||
}
|
|
||||||
|
|
||||||
*index = (int)(filepath - origpath);
|
|
||||||
// parent is now the parent diretory of the file!
|
|
||||||
return *parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
File SDClass::open(const char *filepath, uint8_t mode) {
|
|
||||||
/*
|
|
||||||
|
|
||||||
Open the supplied file path for reading or writing.
|
|
||||||
|
|
||||||
The file content can be accessed via the `file` property of
|
|
||||||
the `SDClass` object--this property is currently
|
|
||||||
a standard `SdFile` object from `sdfatlib`.
|
|
||||||
|
|
||||||
Defaults to read only.
|
|
||||||
|
|
||||||
If `write` is true, default action (when `append` is true) is to
|
|
||||||
append data to the end of the file.
|
|
||||||
|
|
||||||
If `append` is false then the file will be truncated first.
|
|
||||||
|
|
||||||
If the file does not exist and it is opened for writing the file
|
|
||||||
will be created.
|
|
||||||
|
|
||||||
An attempt to open a file for reading that does not exist is an
|
|
||||||
error.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
int pathidx;
|
|
||||||
|
|
||||||
// do the interative search
|
|
||||||
SdFile parentdir = getParentDir(filepath, &pathidx);
|
|
||||||
// no more subdirs!
|
|
||||||
|
|
||||||
filepath += pathidx;
|
|
||||||
|
|
||||||
if (! filepath[0]) {
|
|
||||||
// it was the directory itself!
|
|
||||||
return File(parentdir, "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open the file itself
|
|
||||||
SdFile file;
|
|
||||||
|
|
||||||
// failed to open a subdir!
|
|
||||||
if (!parentdir.isOpen())
|
|
||||||
return File();
|
|
||||||
|
|
||||||
// there is a special case for the Root directory since its a static dir
|
|
||||||
if (parentdir.isRoot()) {
|
|
||||||
if ( ! file.open(root, filepath, mode)) {
|
|
||||||
// failed to open the file :(
|
|
||||||
return File();
|
|
||||||
}
|
|
||||||
// dont close the root!
|
|
||||||
} else {
|
|
||||||
if ( ! file.open(parentdir, filepath, mode)) {
|
|
||||||
return File();
|
|
||||||
}
|
|
||||||
// close the parent
|
|
||||||
parentdir.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode & (O_APPEND | O_WRITE))
|
|
||||||
file.seekSet(file.fileSize());
|
|
||||||
return File(file, filepath);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
File SDClass::open(char *filepath, uint8_t mode) {
|
|
||||||
//
|
|
||||||
|
|
||||||
Open the supplied file path for reading or writing.
|
|
||||||
|
|
||||||
The file content can be accessed via the `file` property of
|
|
||||||
the `SDClass` object--this property is currently
|
|
||||||
a standard `SdFile` object from `sdfatlib`.
|
|
||||||
|
|
||||||
Defaults to read only.
|
|
||||||
|
|
||||||
If `write` is true, default action (when `append` is true) is to
|
|
||||||
append data to the end of the file.
|
|
||||||
|
|
||||||
If `append` is false then the file will be truncated first.
|
|
||||||
|
|
||||||
If the file does not exist and it is opened for writing the file
|
|
||||||
will be created.
|
|
||||||
|
|
||||||
An attempt to open a file for reading that does not exist is an
|
|
||||||
error.
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
// TODO: Allow for read&write? (Possibly not, as it requires seek.)
|
|
||||||
|
|
||||||
fileOpenMode = mode;
|
|
||||||
walkPath(filepath, root, callback_openPath, this);
|
|
||||||
|
|
||||||
return File();
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
//boolean SDClass::close() {
|
|
||||||
// /*
|
|
||||||
//
|
|
||||||
// Closes the file opened by the `open` method.
|
|
||||||
//
|
|
||||||
// */
|
|
||||||
// file.close();
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
boolean SDClass::exists(const char *filepath) {
|
|
||||||
/*
|
|
||||||
|
|
||||||
Returns true if the supplied file path exists.
|
|
||||||
|
|
||||||
*/
|
|
||||||
return walkPath(filepath, root, callback_pathExists);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//boolean SDClass::exists(char *filepath, SdFile& parentDir) {
|
|
||||||
// /*
|
|
||||||
//
|
|
||||||
// Returns true if the supplied file path rooted at `parentDir`
|
|
||||||
// exists.
|
|
||||||
//
|
|
||||||
// */
|
|
||||||
// return walkPath(filepath, parentDir, callback_pathExists);
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
boolean SDClass::mkdir(const char *filepath) {
|
|
||||||
/*
|
|
||||||
|
|
||||||
Makes a single directory or a heirarchy of directories.
|
|
||||||
|
|
||||||
A rough equivalent to `mkdir -p`.
|
|
||||||
|
|
||||||
*/
|
|
||||||
return walkPath(filepath, root, callback_makeDirPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean SDClass::rmdir(const char *filepath) {
|
|
||||||
/*
|
|
||||||
|
|
||||||
Remove a single directory or a heirarchy of directories.
|
|
||||||
|
|
||||||
A rough equivalent to `rm -rf`.
|
|
||||||
|
|
||||||
*/
|
|
||||||
return walkPath(filepath, root, callback_rmdir);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean SDClass::remove(const char *filepath) {
|
|
||||||
return walkPath(filepath, root, callback_remove);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// allows you to recurse into a directory
|
|
||||||
File File::openNextFile(uint8_t mode) {
|
|
||||||
dir_t p;
|
|
||||||
|
|
||||||
//Serial.print("\t\treading dir...");
|
|
||||||
while (_file->readDir(&p) > 0) {
|
|
||||||
|
|
||||||
// done if past last used entry
|
|
||||||
if (p.name[0] == DIR_NAME_FREE) {
|
|
||||||
//Serial.println("end");
|
|
||||||
return File();
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip deleted entry and entries for . and ..
|
|
||||||
if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') {
|
|
||||||
//Serial.println("dots");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// only list subdirectories and files
|
|
||||||
if (!DIR_IS_FILE_OR_SUBDIR(&p)) {
|
|
||||||
//Serial.println("notafile");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// print file name with possible blank fill
|
|
||||||
SdFile f;
|
|
||||||
char name[13];
|
|
||||||
_file->dirName(p, name);
|
|
||||||
//Serial.print("try to open file ");
|
|
||||||
//Serial.println(name);
|
|
||||||
|
|
||||||
if (f.open(_file, name, mode)) {
|
|
||||||
//Serial.println("OK!");
|
|
||||||
return File(f, name);
|
|
||||||
} else {
|
|
||||||
//Serial.println("ugh");
|
|
||||||
return File();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Serial.println("nothing");
|
|
||||||
return File();
|
|
||||||
}
|
|
||||||
|
|
||||||
void File::rewindDirectory(void) {
|
|
||||||
if (isDirectory())
|
|
||||||
_file->rewind();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
|
||||||
SDClass SD;
|
SDClass SD;
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,154 +1,133 @@
|
|||||||
/*
|
/*
|
||||||
|
SD.h - A thin shim for Arduino ESP8266 Filesystems
|
||||||
|
Copyright (c) 2019 Earle F. Philhower, III. All rights reserved.
|
||||||
|
|
||||||
SD - a slightly more friendly wrapper for sdfatlib
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
This library aims to expose a subset of SD card functionality
|
This library is distributed in the hope that it will be useful,
|
||||||
in the form of a higher level "wrapper" object.
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
License: GNU General Public License V3
|
Lesser General Public License for more details.
|
||||||
(Because sdfatlib is licensed with this.)
|
|
||||||
|
|
||||||
(C) Copyright 2010 SparkFun Electronics
|
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __SD_H__
|
#ifndef __SD_H__
|
||||||
#define __SD_H__
|
#define __SD_H__
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <FS.h>
|
||||||
|
#include <SDFS.h>
|
||||||
|
|
||||||
#include <utility/SdFat.h>
|
#undef FILE_READ
|
||||||
#include <utility/SdFatUtil.h>
|
#define FILE_READ sdfat::O_READ
|
||||||
|
#undef FILE_WRITE
|
||||||
#define FILE_READ O_READ
|
#define FILE_WRITE (sdfat::O_READ | sdfat::O_WRITE | sdfat::O_CREAT)
|
||||||
#define FILE_WRITE (O_READ | O_WRITE | O_CREAT)
|
|
||||||
|
|
||||||
class File : public Stream {
|
|
||||||
private:
|
|
||||||
char _name[13]; // our name
|
|
||||||
SdFile *_file; // underlying file pointer
|
|
||||||
|
|
||||||
public:
|
|
||||||
File(SdFile f, const char *name); // wraps an underlying SdFile
|
|
||||||
File(void); // 'empty' constructor
|
|
||||||
virtual size_t write(uint8_t);
|
|
||||||
virtual size_t write(const uint8_t *buf, size_t size);
|
|
||||||
virtual int read();
|
|
||||||
virtual size_t readBytes(char *buffer, size_t length);
|
|
||||||
virtual int peek();
|
|
||||||
virtual int available();
|
|
||||||
virtual void flush();
|
|
||||||
int read(void *buf, uint16_t nbyte);
|
|
||||||
boolean seek(uint32_t pos);
|
|
||||||
uint32_t position();
|
|
||||||
uint32_t size();
|
|
||||||
void close();
|
|
||||||
operator bool();
|
|
||||||
char * name();
|
|
||||||
|
|
||||||
boolean isDirectory(void);
|
|
||||||
File openNextFile(uint8_t mode = O_RDONLY);
|
|
||||||
void rewindDirectory(void);
|
|
||||||
|
|
||||||
template<typename T> size_t write(T &src){
|
|
||||||
uint8_t obuf[512];
|
|
||||||
size_t doneLen = 0;
|
|
||||||
size_t sentLen;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
while (src.available() > 512){
|
|
||||||
src.read(obuf, 512);
|
|
||||||
sentLen = write(obuf, 512);
|
|
||||||
doneLen = doneLen + sentLen;
|
|
||||||
if(sentLen != 512){
|
|
||||||
return doneLen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t leftLen = src.available();
|
|
||||||
src.read(obuf, leftLen);
|
|
||||||
sentLen = write(obuf, leftLen);
|
|
||||||
doneLen = doneLen + sentLen;
|
|
||||||
return doneLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
using Print::write;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SDClass {
|
class SDClass {
|
||||||
|
|
||||||
private:
|
|
||||||
// These are required for initialisation and use of sdfatlib
|
|
||||||
Sd2Card card;
|
|
||||||
SdVolume volume;
|
|
||||||
SdFile root;
|
|
||||||
|
|
||||||
// my quick&dirty iterator, should be replaced
|
|
||||||
SdFile getParentDir(const char *filepath, int *indx);
|
|
||||||
public:
|
public:
|
||||||
// This needs to be called to set up the connection to the SD card
|
boolean begin(uint8_t csPin, SPISettings cfg = SPI_HALF_SPEED) {
|
||||||
// before other methods are used.
|
SDFS.setConfig(SDFSConfig(csPin, cfg));
|
||||||
boolean begin(uint8_t csPin = SD_CHIP_SELECT_PIN, uint32_t speed = SPI_HALF_SPEED);
|
return (boolean)SDFS.begin();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
void end(bool endSPI = true) {
|
||||||
In the following sequence:
|
SDFS.end();
|
||||||
//Insert SD Card A
|
if (endSPI) {
|
||||||
SD.begin()
|
SPI.end();
|
||||||
//do operations
|
}
|
||||||
//remove card A
|
}
|
||||||
//insert SD card B
|
|
||||||
SD.end()
|
|
||||||
|
|
||||||
It is possible that card A becomes corrupted due to removal before calling SD.end().
|
File open(const char *filename, uint8_t mode = FILE_READ) {
|
||||||
It is possible that card B becomes corrupted if there were ops pending for card A at the time SD.end() is called.
|
return SDFS.open(filename, getMode(mode));
|
||||||
|
}
|
||||||
|
|
||||||
Call SD.end() or SD.end(true) to shut everything down.
|
File open(const String &filename, uint8_t mode = FILE_READ) {
|
||||||
Call SD.end(false) to shut everything but the SPI object down.
|
return open(filename.c_str(), mode);
|
||||||
*/
|
}
|
||||||
void end(bool endSPI = true);
|
|
||||||
|
|
||||||
|
boolean exists(const char *filepath) {
|
||||||
|
return (boolean)SDFS.exists(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
// Open the specified file/directory with the supplied mode (e.g. read or
|
boolean exists(const String &filepath) {
|
||||||
// write, etc). Returns a File object for interacting with the file.
|
return (boolean)SDFS.exists(filepath.c_str());
|
||||||
// Note that currently only one file can be open at a time.
|
}
|
||||||
File open(const char *filename, uint8_t mode = FILE_READ);
|
|
||||||
File open(const String &filename, uint8_t mode = FILE_READ) { return open( filename.c_str(), mode ); }
|
|
||||||
|
|
||||||
// Methods to determine if the requested file path exists.
|
boolean mkdir(const char *filepath) {
|
||||||
boolean exists(const char *filepath);
|
return (boolean)SDFS.mkdir(filepath);
|
||||||
boolean exists(const String &filepath) { return exists(filepath.c_str()); }
|
}
|
||||||
|
|
||||||
// Create the requested directory heirarchy--if intermediate directories
|
boolean mkdir(const String &filepath) {
|
||||||
// do not exist they will be created.
|
return (boolean)SDFS.mkdir(filepath.c_str());
|
||||||
boolean mkdir(const char *filepath);
|
}
|
||||||
boolean mkdir(const String &filepath) { return mkdir(filepath.c_str()); }
|
|
||||||
|
|
||||||
// Delete the file.
|
boolean remove(const char *filepath) {
|
||||||
boolean remove(const char *filepath);
|
return (boolean)SDFS.remove(filepath);
|
||||||
boolean remove(const String &filepath) { return remove(filepath.c_str()); }
|
}
|
||||||
|
|
||||||
boolean rmdir(const char *filepath);
|
boolean remove(const String &filepath) {
|
||||||
boolean rmdir(const String &filepath) { return rmdir(filepath.c_str()); }
|
return remove(filepath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean rmdir(const char *filepath) {
|
||||||
|
return (boolean)SDFS.rmdir(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean rmdir(const String &filepath) {
|
||||||
|
return rmdir(filepath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t type() {
|
||||||
|
return 0;//card.type();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t fatType() {
|
||||||
|
return 0;//volume.fatType();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t blocksPerCluster() {
|
||||||
|
return 0;//volume.blocksPerCluster();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t totalClusters() {
|
||||||
|
return 0;//volume.clusterCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t blockSize() {
|
||||||
|
return 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t totalBlocks() {
|
||||||
|
return 0;//(totalClusters() / blocksPerCluster());
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t clusterSize() {
|
||||||
|
return 0;//blocksPerCluster() * blockSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() {
|
||||||
|
return 0;//(clusterSize() * totalClusters());
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t type(){ return card.type(); }
|
|
||||||
uint8_t fatType(){ return volume.fatType(); }
|
|
||||||
size_t blocksPerCluster(){ return volume.blocksPerCluster(); }
|
|
||||||
size_t totalClusters(){ return volume.clusterCount(); }
|
|
||||||
size_t blockSize(){ return (size_t)0x200; }
|
|
||||||
size_t totalBlocks(){ return (totalClusters() / blocksPerCluster()); }
|
|
||||||
size_t clusterSize(){ return blocksPerCluster() * blockSize(); }
|
|
||||||
size_t size(){ return (clusterSize() * totalClusters()); }
|
|
||||||
private:
|
private:
|
||||||
|
const char *getMode(uint8_t mode) {
|
||||||
|
bool read = (mode & sdfat::O_READ) ? true : false;
|
||||||
|
bool write = (mode & sdfat::O_WRITE) ? true : false;
|
||||||
|
bool append = (mode & sdfat::O_APPEND) ? true : false;
|
||||||
|
if ( read & !write ) { return "r"; }
|
||||||
|
else if ( !read & write & !append ) { return "w+"; }
|
||||||
|
else if ( !read & write & append ) { return "a"; }
|
||||||
|
else if ( read & write & !append ) { return "w+"; } // may be a bug in FS::mode interpretation, "r+" seems proper
|
||||||
|
else if ( read & write & append ) { return "a+"; }
|
||||||
|
else { return "r"; }
|
||||||
|
}
|
||||||
|
|
||||||
// This is used to determine the mode used to open a file
|
|
||||||
// it's here because it's the easiest place to pass the
|
|
||||||
// information through the directory walking function. But
|
|
||||||
// it's probably not the best place for it.
|
|
||||||
// It shouldn't be set directly--it is set via the parameters to `open`.
|
|
||||||
int fileOpenMode;
|
|
||||||
|
|
||||||
friend class File;
|
|
||||||
friend boolean callback_openPath(SdFile&, char *, boolean, void *);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
|
||||||
|
@ -1,418 +0,0 @@
|
|||||||
/* Arduino SdFat Library
|
|
||||||
* Copyright (C) 2009 by William Greiman
|
|
||||||
*
|
|
||||||
* This file is part of the Arduino SdFat Library
|
|
||||||
*
|
|
||||||
* This Library is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This Library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with the Arduino SdFat Library. If not, see
|
|
||||||
* <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#ifndef FatStructs_h
|
|
||||||
#define FatStructs_h
|
|
||||||
/**
|
|
||||||
* \file
|
|
||||||
* FAT file structures
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
* mostly from Microsoft document fatgen103.doc
|
|
||||||
* http://www.microsoft.com/whdc/system/platform/firmware/fatgen.mspx
|
|
||||||
*/
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/** Value for byte 510 of boot block or MBR */
|
|
||||||
uint8_t const BOOTSIG0 = 0X55;
|
|
||||||
/** Value for byte 511 of boot block or MBR */
|
|
||||||
uint8_t const BOOTSIG1 = 0XAA;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* \struct partitionTable
|
|
||||||
* \brief MBR partition table entry
|
|
||||||
*
|
|
||||||
* A partition table entry for a MBR formatted storage device.
|
|
||||||
* The MBR partition table has four entries.
|
|
||||||
*/
|
|
||||||
struct partitionTable {
|
|
||||||
/**
|
|
||||||
* Boot Indicator . Indicates whether the volume is the active
|
|
||||||
* partition. Legal values include: 0X00. Do not use for booting.
|
|
||||||
* 0X80 Active partition.
|
|
||||||
*/
|
|
||||||
uint8_t boot;
|
|
||||||
/**
|
|
||||||
* Head part of Cylinder-head-sector address of the first block in
|
|
||||||
* the partition. Legal values are 0-255. Only used in old PC BIOS.
|
|
||||||
*/
|
|
||||||
uint8_t beginHead;
|
|
||||||
/**
|
|
||||||
* Sector part of Cylinder-head-sector address of the first block in
|
|
||||||
* the partition. Legal values are 1-63. Only used in old PC BIOS.
|
|
||||||
*/
|
|
||||||
unsigned beginSector : 6;
|
|
||||||
/** High bits cylinder for first block in partition. */
|
|
||||||
unsigned beginCylinderHigh : 2;
|
|
||||||
/**
|
|
||||||
* Combine beginCylinderLow with beginCylinderHigh. Legal values
|
|
||||||
* are 0-1023. Only used in old PC BIOS.
|
|
||||||
*/
|
|
||||||
uint8_t beginCylinderLow;
|
|
||||||
/**
|
|
||||||
* Partition type. See defines that begin with PART_TYPE_ for
|
|
||||||
* some Microsoft partition types.
|
|
||||||
*/
|
|
||||||
uint8_t type;
|
|
||||||
/**
|
|
||||||
* head part of cylinder-head-sector address of the last sector in the
|
|
||||||
* partition. Legal values are 0-255. Only used in old PC BIOS.
|
|
||||||
*/
|
|
||||||
uint8_t endHead;
|
|
||||||
/**
|
|
||||||
* Sector part of cylinder-head-sector address of the last sector in
|
|
||||||
* the partition. Legal values are 1-63. Only used in old PC BIOS.
|
|
||||||
*/
|
|
||||||
unsigned endSector : 6;
|
|
||||||
/** High bits of end cylinder */
|
|
||||||
unsigned endCylinderHigh : 2;
|
|
||||||
/**
|
|
||||||
* Combine endCylinderLow with endCylinderHigh. Legal values
|
|
||||||
* are 0-1023. Only used in old PC BIOS.
|
|
||||||
*/
|
|
||||||
uint8_t endCylinderLow;
|
|
||||||
/** Logical block address of the first block in the partition. */
|
|
||||||
uint32_t firstSector;
|
|
||||||
/** Length of the partition, in blocks. */
|
|
||||||
uint32_t totalSectors;
|
|
||||||
} __attribute__((packed));
|
|
||||||
/** Type name for partitionTable */
|
|
||||||
typedef struct partitionTable part_t;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* \struct masterBootRecord
|
|
||||||
*
|
|
||||||
* \brief Master Boot Record
|
|
||||||
*
|
|
||||||
* The first block of a storage device that is formatted with a MBR.
|
|
||||||
*/
|
|
||||||
struct masterBootRecord {
|
|
||||||
/** Code Area for master boot program. */
|
|
||||||
uint8_t codeArea[440];
|
|
||||||
/** Optional WindowsNT disk signature. May contain more boot code. */
|
|
||||||
uint32_t diskSignature;
|
|
||||||
/** Usually zero but may be more boot code. */
|
|
||||||
uint16_t usuallyZero;
|
|
||||||
/** Partition tables. */
|
|
||||||
part_t part[4];
|
|
||||||
/** First MBR signature byte. Must be 0X55 */
|
|
||||||
uint8_t mbrSig0;
|
|
||||||
/** Second MBR signature byte. Must be 0XAA */
|
|
||||||
uint8_t mbrSig1;
|
|
||||||
} __attribute__((packed));
|
|
||||||
/** Type name for masterBootRecord */
|
|
||||||
typedef struct masterBootRecord mbr_t;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* \struct biosParmBlock
|
|
||||||
*
|
|
||||||
* \brief BIOS parameter block
|
|
||||||
*
|
|
||||||
* The BIOS parameter block describes the physical layout of a FAT volume.
|
|
||||||
*/
|
|
||||||
struct biosParmBlock {
|
|
||||||
/**
|
|
||||||
* Count of bytes per sector. This value may take on only the
|
|
||||||
* following values: 512, 1024, 2048 or 4096
|
|
||||||
*/
|
|
||||||
uint16_t bytesPerSector;
|
|
||||||
/**
|
|
||||||
* Number of sectors per allocation unit. This value must be a
|
|
||||||
* power of 2 that is greater than 0. The legal values are
|
|
||||||
* 1, 2, 4, 8, 16, 32, 64, and 128.
|
|
||||||
*/
|
|
||||||
uint8_t sectorsPerCluster;
|
|
||||||
/**
|
|
||||||
* Number of sectors before the first FAT.
|
|
||||||
* This value must not be zero.
|
|
||||||
*/
|
|
||||||
uint16_t reservedSectorCount;
|
|
||||||
/** The count of FAT data structures on the volume. This field should
|
|
||||||
* always contain the value 2 for any FAT volume of any type.
|
|
||||||
*/
|
|
||||||
uint8_t fatCount;
|
|
||||||
/**
|
|
||||||
* For FAT12 and FAT16 volumes, this field contains the count of
|
|
||||||
* 32-byte directory entries in the root directory. For FAT32 volumes,
|
|
||||||
* this field must be set to 0. For FAT12 and FAT16 volumes, this
|
|
||||||
* value should always specify a count that when multiplied by 32
|
|
||||||
* results in a multiple of bytesPerSector. FAT16 volumes should
|
|
||||||
* use the value 512.
|
|
||||||
*/
|
|
||||||
uint16_t rootDirEntryCount;
|
|
||||||
/**
|
|
||||||
* This field is the old 16-bit total count of sectors on the volume.
|
|
||||||
* This count includes the count of all sectors in all four regions
|
|
||||||
* of the volume. This field can be 0; if it is 0, then totalSectors32
|
|
||||||
* must be non-zero. For FAT32 volumes, this field must be 0. For
|
|
||||||
* FAT12 and FAT16 volumes, this field contains the sector count, and
|
|
||||||
* totalSectors32 is 0 if the total sector count fits
|
|
||||||
* (is less than 0x10000).
|
|
||||||
*/
|
|
||||||
uint16_t totalSectors16;
|
|
||||||
/**
|
|
||||||
* This dates back to the old MS-DOS 1.x media determination and is
|
|
||||||
* no longer usually used for anything. 0xF8 is the standard value
|
|
||||||
* for fixed (non-removable) media. For removable media, 0xF0 is
|
|
||||||
* frequently used. Legal values are 0xF0 or 0xF8-0xFF.
|
|
||||||
*/
|
|
||||||
uint8_t mediaType;
|
|
||||||
/**
|
|
||||||
* Count of sectors occupied by one FAT on FAT12/FAT16 volumes.
|
|
||||||
* On FAT32 volumes this field must be 0, and sectorsPerFat32
|
|
||||||
* contains the FAT size count.
|
|
||||||
*/
|
|
||||||
uint16_t sectorsPerFat16;
|
|
||||||
/** Sectors per track for interrupt 0x13. Not used otherwise. */
|
|
||||||
uint16_t sectorsPerTrtack;
|
|
||||||
/** Number of heads for interrupt 0x13. Not used otherwise. */
|
|
||||||
uint16_t headCount;
|
|
||||||
/**
|
|
||||||
* Count of hidden sectors preceding the partition that contains this
|
|
||||||
* FAT volume. This field is generally only relevant for media
|
|
||||||
* visible on interrupt 0x13.
|
|
||||||
*/
|
|
||||||
uint32_t hidddenSectors;
|
|
||||||
/**
|
|
||||||
* This field is the new 32-bit total count of sectors on the volume.
|
|
||||||
* This count includes the count of all sectors in all four regions
|
|
||||||
* of the volume. This field can be 0; if it is 0, then
|
|
||||||
* totalSectors16 must be non-zero.
|
|
||||||
*/
|
|
||||||
uint32_t totalSectors32;
|
|
||||||
/**
|
|
||||||
* Count of sectors occupied by one FAT on FAT32 volumes.
|
|
||||||
*/
|
|
||||||
uint32_t sectorsPerFat32;
|
|
||||||
/**
|
|
||||||
* This field is only defined for FAT32 media and does not exist on
|
|
||||||
* FAT12 and FAT16 media.
|
|
||||||
* Bits 0-3 -- Zero-based number of active FAT.
|
|
||||||
* Only valid if mirroring is disabled.
|
|
||||||
* Bits 4-6 -- Reserved.
|
|
||||||
* Bit 7 -- 0 means the FAT is mirrored at runtime into all FATs.
|
|
||||||
* -- 1 means only one FAT is active; it is the one referenced in bits 0-3.
|
|
||||||
* Bits 8-15 -- Reserved.
|
|
||||||
*/
|
|
||||||
uint16_t fat32Flags;
|
|
||||||
/**
|
|
||||||
* FAT32 version. High byte is major revision number.
|
|
||||||
* Low byte is minor revision number. Only 0.0 define.
|
|
||||||
*/
|
|
||||||
uint16_t fat32Version;
|
|
||||||
/**
|
|
||||||
* Cluster number of the first cluster of the root directory for FAT32.
|
|
||||||
* This usually 2 but not required to be 2.
|
|
||||||
*/
|
|
||||||
uint32_t fat32RootCluster;
|
|
||||||
/**
|
|
||||||
* Sector number of FSINFO structure in the reserved area of the
|
|
||||||
* FAT32 volume. Usually 1.
|
|
||||||
*/
|
|
||||||
uint16_t fat32FSInfo;
|
|
||||||
/**
|
|
||||||
* If non-zero, indicates the sector number in the reserved area
|
|
||||||
* of the volume of a copy of the boot record. Usually 6.
|
|
||||||
* No value other than 6 is recommended.
|
|
||||||
*/
|
|
||||||
uint16_t fat32BackBootBlock;
|
|
||||||
/**
|
|
||||||
* Reserved for future expansion. Code that formats FAT32 volumes
|
|
||||||
* should always set all of the bytes of this field to 0.
|
|
||||||
*/
|
|
||||||
uint8_t fat32Reserved[12];
|
|
||||||
} __attribute__((packed));
|
|
||||||
/** Type name for biosParmBlock */
|
|
||||||
typedef struct biosParmBlock bpb_t;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* \struct fat32BootSector
|
|
||||||
*
|
|
||||||
* \brief Boot sector for a FAT16 or FAT32 volume.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
struct fat32BootSector {
|
|
||||||
/** X86 jmp to boot program */
|
|
||||||
uint8_t jmpToBootCode[3];
|
|
||||||
/** informational only - don't depend on it */
|
|
||||||
char oemName[8];
|
|
||||||
/** BIOS Parameter Block */
|
|
||||||
bpb_t bpb;
|
|
||||||
/** for int0x13 use value 0X80 for hard drive */
|
|
||||||
uint8_t driveNumber;
|
|
||||||
/** used by Windows NT - should be zero for FAT */
|
|
||||||
uint8_t reserved1;
|
|
||||||
/** 0X29 if next three fields are valid */
|
|
||||||
uint8_t bootSignature;
|
|
||||||
/** usually generated by combining date and time */
|
|
||||||
uint32_t volumeSerialNumber;
|
|
||||||
/** should match volume label in root dir */
|
|
||||||
char volumeLabel[11];
|
|
||||||
/** informational only - don't depend on it */
|
|
||||||
char fileSystemType[8];
|
|
||||||
/** X86 boot code */
|
|
||||||
uint8_t bootCode[420];
|
|
||||||
/** must be 0X55 */
|
|
||||||
uint8_t bootSectorSig0;
|
|
||||||
/** must be 0XAA */
|
|
||||||
uint8_t bootSectorSig1;
|
|
||||||
} __attribute__((packed));
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// End Of Chain values for FAT entries
|
|
||||||
/** FAT16 end of chain value used by Microsoft. */
|
|
||||||
uint16_t const FAT16EOC = 0XFFFF;
|
|
||||||
/** Minimum value for FAT16 EOC. Use to test for EOC. */
|
|
||||||
uint16_t const FAT16EOC_MIN = 0XFFF8;
|
|
||||||
/** FAT32 end of chain value used by Microsoft. */
|
|
||||||
uint32_t const FAT32EOC = 0X0FFFFFFF;
|
|
||||||
/** Minimum value for FAT32 EOC. Use to test for EOC. */
|
|
||||||
uint32_t const FAT32EOC_MIN = 0X0FFFFFF8;
|
|
||||||
/** Mask a for FAT32 entry. Entries are 28 bits. */
|
|
||||||
uint32_t const FAT32MASK = 0X0FFFFFFF;
|
|
||||||
|
|
||||||
/** Type name for fat32BootSector */
|
|
||||||
typedef struct fat32BootSector fbs_t;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* \struct directoryEntry
|
|
||||||
* \brief FAT short directory entry
|
|
||||||
*
|
|
||||||
* Short means short 8.3 name, not the entry size.
|
|
||||||
*
|
|
||||||
* Date Format. A FAT directory entry date stamp is a 16-bit field that is
|
|
||||||
* basically a date relative to the MS-DOS epoch of 01/01/1980. Here is the
|
|
||||||
* format (bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the
|
|
||||||
* 16-bit word):
|
|
||||||
*
|
|
||||||
* Bits 9-15: Count of years from 1980, valid value range 0-127
|
|
||||||
* inclusive (1980-2107).
|
|
||||||
*
|
|
||||||
* Bits 5-8: Month of year, 1 = January, valid value range 1-12 inclusive.
|
|
||||||
*
|
|
||||||
* Bits 0-4: Day of month, valid value range 1-31 inclusive.
|
|
||||||
*
|
|
||||||
* Time Format. A FAT directory entry time stamp is a 16-bit field that has
|
|
||||||
* a granularity of 2 seconds. Here is the format (bit 0 is the LSB of the
|
|
||||||
* 16-bit word, bit 15 is the MSB of the 16-bit word).
|
|
||||||
*
|
|
||||||
* Bits 11-15: Hours, valid value range 0-23 inclusive.
|
|
||||||
*
|
|
||||||
* Bits 5-10: Minutes, valid value range 0-59 inclusive.
|
|
||||||
*
|
|
||||||
* Bits 0-4: 2-second count, valid value range 0-29 inclusive (0 - 58 seconds).
|
|
||||||
*
|
|
||||||
* The valid time range is from Midnight 00:00:00 to 23:59:58.
|
|
||||||
*/
|
|
||||||
struct directoryEntry {
|
|
||||||
/**
|
|
||||||
* Short 8.3 name.
|
|
||||||
* The first eight bytes contain the file name with blank fill.
|
|
||||||
* The last three bytes contain the file extension with blank fill.
|
|
||||||
*/
|
|
||||||
uint8_t name[11];
|
|
||||||
/** Entry attributes.
|
|
||||||
*
|
|
||||||
* The upper two bits of the attribute byte are reserved and should
|
|
||||||
* always be set to 0 when a file is created and never modified or
|
|
||||||
* looked at after that. See defines that begin with DIR_ATT_.
|
|
||||||
*/
|
|
||||||
uint8_t attributes;
|
|
||||||
/**
|
|
||||||
* Reserved for use by Windows NT. Set value to 0 when a file is
|
|
||||||
* created and never modify or look at it after that.
|
|
||||||
*/
|
|
||||||
uint8_t reservedNT;
|
|
||||||
/**
|
|
||||||
* The granularity of the seconds part of creationTime is 2 seconds
|
|
||||||
* so this field is a count of tenths of a second and its valid
|
|
||||||
* value range is 0-199 inclusive. (WHG note - seems to be hundredths)
|
|
||||||
*/
|
|
||||||
uint8_t creationTimeTenths;
|
|
||||||
/** Time file was created. */
|
|
||||||
uint16_t creationTime;
|
|
||||||
/** Date file was created. */
|
|
||||||
uint16_t creationDate;
|
|
||||||
/**
|
|
||||||
* Last access date. Note that there is no last access time, only
|
|
||||||
* a date. This is the date of last read or write. In the case of
|
|
||||||
* a write, this should be set to the same date as lastWriteDate.
|
|
||||||
*/
|
|
||||||
uint16_t lastAccessDate;
|
|
||||||
/**
|
|
||||||
* High word of this entry's first cluster number (always 0 for a
|
|
||||||
* FAT12 or FAT16 volume).
|
|
||||||
*/
|
|
||||||
uint16_t firstClusterHigh;
|
|
||||||
/** Time of last write. File creation is considered a write. */
|
|
||||||
uint16_t lastWriteTime;
|
|
||||||
/** Date of last write. File creation is considered a write. */
|
|
||||||
uint16_t lastWriteDate;
|
|
||||||
/** Low word of this entry's first cluster number. */
|
|
||||||
uint16_t firstClusterLow;
|
|
||||||
/** 32-bit unsigned holding this file's size in bytes. */
|
|
||||||
uint32_t fileSize;
|
|
||||||
} __attribute__((packed));
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Definitions for directory entries
|
|
||||||
//
|
|
||||||
/** Type name for directoryEntry */
|
|
||||||
typedef struct directoryEntry dir_t;
|
|
||||||
/** escape for name[0] = 0XE5 */
|
|
||||||
uint8_t const DIR_NAME_0XE5 = 0X05;
|
|
||||||
/** name[0] value for entry that is free after being "deleted" */
|
|
||||||
uint8_t const DIR_NAME_DELETED = 0XE5;
|
|
||||||
/** name[0] value for entry that is free and no allocated entries follow */
|
|
||||||
uint8_t const DIR_NAME_FREE = 0X00;
|
|
||||||
/** file is read-only */
|
|
||||||
uint8_t const DIR_ATT_READ_ONLY = 0X01;
|
|
||||||
/** File should hidden in directory listings */
|
|
||||||
uint8_t const DIR_ATT_HIDDEN = 0X02;
|
|
||||||
/** Entry is for a system file */
|
|
||||||
uint8_t const DIR_ATT_SYSTEM = 0X04;
|
|
||||||
/** Directory entry contains the volume label */
|
|
||||||
uint8_t const DIR_ATT_VOLUME_ID = 0X08;
|
|
||||||
/** Entry is for a directory */
|
|
||||||
uint8_t const DIR_ATT_DIRECTORY = 0X10;
|
|
||||||
/** Old DOS archive bit for backup support */
|
|
||||||
uint8_t const DIR_ATT_ARCHIVE = 0X20;
|
|
||||||
/** Test value for long name entry. Test is
|
|
||||||
(d->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME. */
|
|
||||||
uint8_t const DIR_ATT_LONG_NAME = 0X0F;
|
|
||||||
/** Test mask for long name entry */
|
|
||||||
uint8_t const DIR_ATT_LONG_NAME_MASK = 0X3F;
|
|
||||||
/** defined attribute bits */
|
|
||||||
uint8_t const DIR_ATT_DEFINED_BITS = 0X3F;
|
|
||||||
/** Directory entry is part of a long name */
|
|
||||||
static inline uint8_t DIR_IS_LONG_NAME(const dir_t* dir) {
|
|
||||||
return (dir->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME;
|
|
||||||
}
|
|
||||||
/** Mask for file/subdirectory tests */
|
|
||||||
uint8_t const DIR_ATT_FILE_TYPE_MASK = (DIR_ATT_VOLUME_ID | DIR_ATT_DIRECTORY);
|
|
||||||
/** Directory entry is for a file */
|
|
||||||
static inline uint8_t DIR_IS_FILE(const dir_t* dir) {
|
|
||||||
return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == 0;
|
|
||||||
}
|
|
||||||
/** Directory entry is for a subdirectory */
|
|
||||||
static inline uint8_t DIR_IS_SUBDIR(const dir_t* dir) {
|
|
||||||
return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == DIR_ATT_DIRECTORY;
|
|
||||||
}
|
|
||||||
/** Directory entry is for a file or subdirectory */
|
|
||||||
static inline uint8_t DIR_IS_FILE_OR_SUBDIR(const dir_t* dir) {
|
|
||||||
return (dir->attributes & DIR_ATT_VOLUME_ID) == 0;
|
|
||||||
}
|
|
||||||
#endif // FatStructs_h
|
|
@ -1,780 +0,0 @@
|
|||||||
/* Arduino Sd2Card Library
|
|
||||||
* Copyright (C) 2009 by William Greiman
|
|
||||||
*
|
|
||||||
* This file is part of the Arduino Sd2Card 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 Sd2Card Library. If not, see
|
|
||||||
* <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#define USE_SPI_LIB
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include "Sd2Card.h"
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
#ifndef SOFTWARE_SPI
|
|
||||||
#ifdef USE_SPI_LIB
|
|
||||||
#include <SPI.h>
|
|
||||||
static SPISettings settings;
|
|
||||||
#endif
|
|
||||||
// functions for hardware SPI
|
|
||||||
/** Send a byte to the card */
|
|
||||||
static void spiSend(uint8_t b) {
|
|
||||||
#ifndef USE_SPI_LIB
|
|
||||||
SPDR = b;
|
|
||||||
while (!(SPSR & (1 << SPIF)))
|
|
||||||
;
|
|
||||||
#else
|
|
||||||
#ifdef ESP8266
|
|
||||||
SPI.write(b);
|
|
||||||
#else
|
|
||||||
SPI.transfer(b);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
/** Receive a byte from the card */
|
|
||||||
static uint8_t spiRec(void) {
|
|
||||||
#ifndef USE_SPI_LIB
|
|
||||||
spiSend(0XFF);
|
|
||||||
return SPDR;
|
|
||||||
#else
|
|
||||||
return SPI.transfer(0xFF);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#else // SOFTWARE_SPI
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/** nop to tune soft SPI timing */
|
|
||||||
#define nop asm volatile ("nop\n\t")
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/** Soft SPI receive */
|
|
||||||
uint8_t spiRec(void) {
|
|
||||||
uint8_t data = 0;
|
|
||||||
// no interrupts during byte receive - about 8 us
|
|
||||||
cli();
|
|
||||||
// output pin high - like sending 0XFF
|
|
||||||
fastDigitalWrite(SPI_MOSI_PIN, HIGH);
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < 8; i++) {
|
|
||||||
fastDigitalWrite(SPI_SCK_PIN, HIGH);
|
|
||||||
|
|
||||||
// adjust so SCK is nice
|
|
||||||
nop;
|
|
||||||
nop;
|
|
||||||
|
|
||||||
data <<= 1;
|
|
||||||
|
|
||||||
if (fastDigitalRead(SPI_MISO_PIN)) data |= 1;
|
|
||||||
|
|
||||||
fastDigitalWrite(SPI_SCK_PIN, LOW);
|
|
||||||
}
|
|
||||||
// enable interrupts
|
|
||||||
sei();
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/** Soft SPI send */
|
|
||||||
void spiSend(uint8_t data) {
|
|
||||||
// no interrupts during byte send - about 8 us
|
|
||||||
cli();
|
|
||||||
for (uint8_t i = 0; i < 8; i++) {
|
|
||||||
fastDigitalWrite(SPI_SCK_PIN, LOW);
|
|
||||||
|
|
||||||
fastDigitalWrite(SPI_MOSI_PIN, data & 0X80);
|
|
||||||
|
|
||||||
data <<= 1;
|
|
||||||
|
|
||||||
fastDigitalWrite(SPI_SCK_PIN, HIGH);
|
|
||||||
}
|
|
||||||
// hold SCK high for a few ns
|
|
||||||
nop;
|
|
||||||
nop;
|
|
||||||
nop;
|
|
||||||
nop;
|
|
||||||
|
|
||||||
fastDigitalWrite(SPI_SCK_PIN, LOW);
|
|
||||||
// enable interrupts
|
|
||||||
sei();
|
|
||||||
}
|
|
||||||
#endif // SOFTWARE_SPI
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// send command and return error code. Return zero for OK
|
|
||||||
uint8_t Sd2Card::cardCommand(uint8_t cmd, uint32_t arg) {
|
|
||||||
// end read if in partialBlockRead mode
|
|
||||||
readEnd();
|
|
||||||
|
|
||||||
// select card
|
|
||||||
chipSelectLow();
|
|
||||||
|
|
||||||
// wait up to 300 ms if busy
|
|
||||||
waitNotBusy(300);
|
|
||||||
|
|
||||||
// send command
|
|
||||||
spiSend(cmd | 0x40);
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
// send argument
|
|
||||||
SPI.write32(arg, true);
|
|
||||||
#else
|
|
||||||
// send argument
|
|
||||||
for (int8_t s = 24; s >= 0; s -= 8) spiSend(arg >> s);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
// send CRC
|
|
||||||
uint8_t crc = 0xFF;
|
|
||||||
if (cmd == CMD0) crc = 0x95; // correct crc for CMD0 with arg 0
|
|
||||||
if (cmd == CMD8) crc = 0x87; // correct crc for CMD8 with arg 0X1AA
|
|
||||||
spiSend(crc);
|
|
||||||
|
|
||||||
// wait for response
|
|
||||||
for (uint8_t i = 0; ((status_ = spiRec()) & 0x80) && i != 0xFF; i++)
|
|
||||||
;
|
|
||||||
#ifdef ESP8266
|
|
||||||
optimistic_yield(10000);
|
|
||||||
#endif
|
|
||||||
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(void) {
|
|
||||||
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 = ((uint32_t)csd.v2.c_size_high << 16)
|
|
||||||
| (csd.v2.c_size_mid << 8) | csd.v2.c_size_low;
|
|
||||||
return (c_size + 1) << 10;
|
|
||||||
} else {
|
|
||||||
error(SD_CARD_ERROR_BAD_CSD);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
static uint8_t chip_select_asserted = 0;
|
|
||||||
|
|
||||||
void Sd2Card::chipSelectHigh(void) {
|
|
||||||
digitalWrite(chipSelectPin_, HIGH);
|
|
||||||
#ifdef USE_SPI_LIB
|
|
||||||
if (chip_select_asserted) {
|
|
||||||
chip_select_asserted = 0;
|
|
||||||
SPI.endTransaction();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
void Sd2Card::chipSelectLow(void) {
|
|
||||||
#ifdef USE_SPI_LIB
|
|
||||||
if (!chip_select_asserted) {
|
|
||||||
chip_select_asserted = 1;
|
|
||||||
SPI.beginTransaction(settings);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
uint8_t Sd2Card::erase(uint32_t firstBlock, uint32_t lastBlock) {
|
|
||||||
if (!eraseSingleBlockEnable()) {
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
uint8_t Sd2Card::eraseSingleBlockEnable(void) {
|
|
||||||
csd_t csd;
|
|
||||||
return readCSD(&csd) ? csd.v1.erase_blk_en : 0;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* 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().
|
|
||||||
*/
|
|
||||||
#ifdef ESP8266
|
|
||||||
uint8_t Sd2Card::init(uint32_t sckRateID, uint8_t chipSelectPin) {
|
|
||||||
#else
|
|
||||||
uint8_t Sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin) {
|
|
||||||
#endif
|
|
||||||
errorCode_ = inBlock_ = partialBlockRead_ = type_ = 0;
|
|
||||||
chipSelectPin_ = chipSelectPin;
|
|
||||||
// 16-bit init start time allows over a minute
|
|
||||||
uint16_t t0 = (uint16_t)millis();
|
|
||||||
uint32_t arg;
|
|
||||||
|
|
||||||
// set pin modes
|
|
||||||
pinMode(chipSelectPin_, OUTPUT);
|
|
||||||
digitalWrite(chipSelectPin_, HIGH);
|
|
||||||
#ifndef USE_SPI_LIB
|
|
||||||
pinMode(SPI_MISO_PIN, INPUT);
|
|
||||||
pinMode(SPI_MOSI_PIN, OUTPUT);
|
|
||||||
pinMode(SPI_SCK_PIN, OUTPUT);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef SOFTWARE_SPI
|
|
||||||
#ifndef USE_SPI_LIB
|
|
||||||
// SS must be in output mode even it is not chip select
|
|
||||||
pinMode(SS_PIN, OUTPUT);
|
|
||||||
digitalWrite(SS_PIN, HIGH); // disable any SPI device using hardware SS pin
|
|
||||||
// Enable SPI, Master, clock rate f_osc/128
|
|
||||||
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
|
|
||||||
// clear double speed
|
|
||||||
SPSR &= ~(1 << SPI2X);
|
|
||||||
#else // USE_SPI_LIB
|
|
||||||
SPI.begin();
|
|
||||||
settings = SPISettings(250000, MSBFIRST, SPI_MODE0);
|
|
||||||
#endif // USE_SPI_LIB
|
|
||||||
#endif // SOFTWARE_SPI
|
|
||||||
|
|
||||||
// must supply min of 74 clock cycles with CS high.
|
|
||||||
#ifdef USE_SPI_LIB
|
|
||||||
SPI.beginTransaction(settings);
|
|
||||||
#endif
|
|
||||||
for (uint8_t i = 0; i < 10; i++) spiSend(0XFF);
|
|
||||||
#ifdef USE_SPI_LIB
|
|
||||||
SPI.endTransaction();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
chipSelectLow();
|
|
||||||
|
|
||||||
// command to go idle in SPI mode
|
|
||||||
while ((status_ = cardCommand(CMD0, 0)) != R1_IDLE_STATE) {
|
|
||||||
if (((uint16_t)(millis() - t0)) > SD_INIT_TIMEOUT) {
|
|
||||||
error(SD_CARD_ERROR_CMD0);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// check SD version
|
|
||||||
if ((cardCommand(CMD8, 0x1AA) & R1_ILLEGAL_COMMAND)) {
|
|
||||||
type(SD_CARD_TYPE_SD1);
|
|
||||||
} else {
|
|
||||||
// only need last byte of r7 response
|
|
||||||
for (uint8_t i = 0; i < 4; i++) status_ = spiRec();
|
|
||||||
if (status_ != 0XAA) {
|
|
||||||
error(SD_CARD_ERROR_CMD8);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
type(SD_CARD_TYPE_SD2);
|
|
||||||
}
|
|
||||||
// initialize card and send host supports SDHC if SD2
|
|
||||||
arg = type() == SD_CARD_TYPE_SD2 ? 0X40000000 : 0;
|
|
||||||
|
|
||||||
while ((status_ = cardAcmd(ACMD41, arg)) != R1_READY_STATE) {
|
|
||||||
// check for timeout
|
|
||||||
if (((uint16_t)(millis() - 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();
|
|
||||||
}
|
|
||||||
chipSelectHigh();
|
|
||||||
|
|
||||||
#ifndef SOFTWARE_SPI
|
|
||||||
return setSckRate(sckRateID);
|
|
||||||
#else // SOFTWARE_SPI
|
|
||||||
return true;
|
|
||||||
#endif // SOFTWARE_SPI
|
|
||||||
|
|
||||||
fail:
|
|
||||||
chipSelectHigh();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Shut down Sd2Card, which at this point means end SPI.
|
|
||||||
*
|
|
||||||
* \param[in] endSPI The value true (non-zero) or FALSE (zero).
|
|
||||||
|
|
||||||
Call card.end() or card.end(true) to shut everything down.
|
|
||||||
Call card.end(false) to shut everything but the SPI object down.
|
|
||||||
*/
|
|
||||||
void Sd2Card::end(bool endSPI)
|
|
||||||
{
|
|
||||||
if(endSPI)
|
|
||||||
SPI.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Enable or disable partial block reads.
|
|
||||||
*
|
|
||||||
* Enabling partial block reads improves performance by allowing a block
|
|
||||||
* to be read over the SPI bus as several sub-blocks. Errors may occur
|
|
||||||
* if the time between reads is too long since the SD card may timeout.
|
|
||||||
* The SPI SS line will be held low until the entire block is read or
|
|
||||||
* readEnd() is called.
|
|
||||||
*
|
|
||||||
* Use this for applications like the Adafruit Wave Shield.
|
|
||||||
*
|
|
||||||
* \param[in] value The value TRUE (non-zero) or FALSE (zero).)
|
|
||||||
*/
|
|
||||||
void Sd2Card::partialBlockRead(uint8_t value) {
|
|
||||||
readEnd();
|
|
||||||
partialBlockRead_ = value;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Read a 512 byte block from an SD card device.
|
|
||||||
*
|
|
||||||
* \param[in] block 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.
|
|
||||||
*/
|
|
||||||
uint8_t Sd2Card::readBlock(uint32_t block, uint8_t* dst) {
|
|
||||||
return readData(block, 0, 512, dst);
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Read part of a 512 byte block from an SD card.
|
|
||||||
*
|
|
||||||
* \param[in] block Logical block to be read.
|
|
||||||
* \param[in] offset Number of bytes to skip at start of block
|
|
||||||
* \param[out] dst Pointer to the location that will receive the data.
|
|
||||||
* \param[in] count Number of bytes to read
|
|
||||||
* \return The value one, true, is returned for success and
|
|
||||||
* the value zero, false, is returned for failure.
|
|
||||||
*/
|
|
||||||
uint8_t Sd2Card::readData(uint32_t block,
|
|
||||||
uint16_t offset, uint16_t count, uint8_t* dst) {
|
|
||||||
if (count == 0) return true;
|
|
||||||
if ((count + offset) > 512) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
if (!inBlock_ || block != block_ || offset < offset_) {
|
|
||||||
block_ = block;
|
|
||||||
// use address if not SDHC card
|
|
||||||
if (type()!= SD_CARD_TYPE_SDHC) block <<= 9;
|
|
||||||
if (cardCommand(CMD17, block)) {
|
|
||||||
error(SD_CARD_ERROR_CMD17);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
if (!waitStartBlock()) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
offset_ = 0;
|
|
||||||
inBlock_ = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef OPTIMIZE_HARDWARE_SPI
|
|
||||||
uint16_t n;
|
|
||||||
|
|
||||||
// start first spi transfer
|
|
||||||
SPDR = 0XFF;
|
|
||||||
|
|
||||||
// skip data before offset
|
|
||||||
for (;offset_ < offset; offset_++) {
|
|
||||||
while (!(SPSR & (1 << SPIF)))
|
|
||||||
;
|
|
||||||
SPDR = 0XFF;
|
|
||||||
}
|
|
||||||
// transfer data
|
|
||||||
n = count - 1;
|
|
||||||
for (uint16_t i = 0; i < n; i++) {
|
|
||||||
while (!(SPSR & (1 << SPIF)))
|
|
||||||
;
|
|
||||||
dst[i] = SPDR;
|
|
||||||
SPDR = 0XFF;
|
|
||||||
}
|
|
||||||
// wait for last byte
|
|
||||||
while (!(SPSR & (1 << SPIF)))
|
|
||||||
;
|
|
||||||
dst[n] = SPDR;
|
|
||||||
|
|
||||||
#else // OPTIMIZE_HARDWARE_SPI
|
|
||||||
#ifdef ESP8266
|
|
||||||
// skip data before offset
|
|
||||||
SPI.transferBytes(NULL, NULL, offset_);
|
|
||||||
|
|
||||||
// transfer data
|
|
||||||
SPI.transferBytes(NULL, dst, count);
|
|
||||||
|
|
||||||
#else
|
|
||||||
// skip data before offset
|
|
||||||
for (;offset_ < offset; offset_++) {
|
|
||||||
spiRec();
|
|
||||||
}
|
|
||||||
// transfer data
|
|
||||||
for (uint16_t i = 0; i < count; i++) {
|
|
||||||
dst[i] = spiRec();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif // OPTIMIZE_HARDWARE_SPI
|
|
||||||
|
|
||||||
offset_ += count;
|
|
||||||
if (!partialBlockRead_ || offset_ >= 512) {
|
|
||||||
// read rest of data, checksum and set chip select high
|
|
||||||
readEnd();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
|
|
||||||
fail:
|
|
||||||
chipSelectHigh();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/** Skip remaining data in a block when in partial block read mode. */
|
|
||||||
void Sd2Card::readEnd(void) {
|
|
||||||
if (inBlock_) {
|
|
||||||
// skip data and crc
|
|
||||||
#ifdef OPTIMIZE_HARDWARE_SPI
|
|
||||||
// optimize skip for hardware
|
|
||||||
SPDR = 0XFF;
|
|
||||||
while (offset_++ < 513) {
|
|
||||||
while (!(SPSR & (1 << SPIF)))
|
|
||||||
;
|
|
||||||
SPDR = 0XFF;
|
|
||||||
}
|
|
||||||
// wait for last crc byte
|
|
||||||
while (!(SPSR & (1 << SPIF)))
|
|
||||||
;
|
|
||||||
#else // OPTIMIZE_HARDWARE_SPI
|
|
||||||
#ifdef ESP8266
|
|
||||||
SPI.transferBytes(NULL, NULL, (514-offset_));
|
|
||||||
#else
|
|
||||||
while (offset_++ < 514) spiRec();
|
|
||||||
#endif
|
|
||||||
#endif // OPTIMIZE_HARDWARE_SPI
|
|
||||||
chipSelectHigh();
|
|
||||||
inBlock_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/** read CID or CSR register */
|
|
||||||
uint8_t 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;
|
|
||||||
}
|
|
||||||
if (!waitStartBlock()) goto fail;
|
|
||||||
// transfer data
|
|
||||||
#ifdef ESP8266
|
|
||||||
SPI.transferBytes(NULL, dst, 16);
|
|
||||||
#else
|
|
||||||
for (uint16_t i = 0; i < 16; i++) dst[i] = spiRec();
|
|
||||||
#endif
|
|
||||||
spiRec(); // get first crc byte
|
|
||||||
spiRec(); // get second crc byte
|
|
||||||
chipSelectHigh();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
fail:
|
|
||||||
chipSelectHigh();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Set the SPI clock rate.
|
|
||||||
*
|
|
||||||
* \param[in] sckRateID A value in the range [0, 6].
|
|
||||||
*
|
|
||||||
* The SPI clock will be set to F_CPU/pow(2, 1 + sckRateID). The maximum
|
|
||||||
* SPI rate is F_CPU/2 for \a sckRateID = 0 and the minimum rate is F_CPU/128
|
|
||||||
* for \a scsRateID = 6.
|
|
||||||
*
|
|
||||||
* \return The value one, true, is returned for success and the value zero,
|
|
||||||
* false, is returned for an invalid value of \a sckRateID.
|
|
||||||
*/
|
|
||||||
#ifdef ESP8266
|
|
||||||
uint8_t Sd2Card::setSckRate(uint32_t sckRateID) {
|
|
||||||
#else
|
|
||||||
uint8_t Sd2Card::setSckRate(uint8_t sckRateID) {
|
|
||||||
if (sckRateID > 6) {
|
|
||||||
error(SD_CARD_ERROR_SCK_RATE);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifndef USE_SPI_LIB
|
|
||||||
// see avr processor datasheet for SPI register bit definitions
|
|
||||||
if ((sckRateID & 1) || sckRateID == 6) {
|
|
||||||
SPSR &= ~(1 << SPI2X);
|
|
||||||
} else {
|
|
||||||
SPSR |= (1 << SPI2X);
|
|
||||||
}
|
|
||||||
SPCR &= ~((1 <<SPR1) | (1 << SPR0));
|
|
||||||
SPCR |= (sckRateID & 4 ? (1 << SPR1) : 0)
|
|
||||||
| (sckRateID & 2 ? (1 << SPR0) : 0);
|
|
||||||
#else // USE_SPI_LIB
|
|
||||||
#ifdef ESP8266
|
|
||||||
settings = SPISettings(sckRateID, MSBFIRST, SPI_MODE0);
|
|
||||||
#else
|
|
||||||
switch (sckRateID) {
|
|
||||||
case 0: settings = SPISettings(25000000, MSBFIRST, SPI_MODE0); break;
|
|
||||||
case 1: settings = SPISettings(4000000, MSBFIRST, SPI_MODE0); break;
|
|
||||||
case 2: settings = SPISettings(2000000, MSBFIRST, SPI_MODE0); break;
|
|
||||||
case 3: settings = SPISettings(1000000, MSBFIRST, SPI_MODE0); break;
|
|
||||||
case 4: settings = SPISettings(500000, MSBFIRST, SPI_MODE0); break;
|
|
||||||
case 5: settings = SPISettings(250000, MSBFIRST, SPI_MODE0); break;
|
|
||||||
default: settings = SPISettings(125000, MSBFIRST, SPI_MODE0);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif // USE_SPI_LIB
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// wait for card to go not busy
|
|
||||||
uint8_t Sd2Card::waitNotBusy(uint16_t timeoutMillis) {
|
|
||||||
uint16_t t0 = millis();
|
|
||||||
do {
|
|
||||||
#ifdef ESP8266
|
|
||||||
optimistic_yield(10000);
|
|
||||||
#endif
|
|
||||||
if (spiRec() == 0XFF) return true;
|
|
||||||
}
|
|
||||||
while (((uint16_t)millis() - t0) < timeoutMillis);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/** Wait for start block token */
|
|
||||||
uint8_t Sd2Card::waitStartBlock(void) {
|
|
||||||
uint16_t t0 = millis();
|
|
||||||
while ((status_ = spiRec()) == 0XFF) {
|
|
||||||
#ifdef ESP8266
|
|
||||||
optimistic_yield(10000);
|
|
||||||
#endif
|
|
||||||
if (((uint16_t)millis() - t0) > SD_READ_TIMEOUT) {
|
|
||||||
error(SD_CARD_ERROR_READ_TIMEOUT);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (status_ != DATA_START_BLOCK) {
|
|
||||||
error(SD_CARD_ERROR_READ);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
|
|
||||||
fail:
|
|
||||||
chipSelectHigh();
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
uint8_t Sd2Card::writeBlock(uint32_t blockNumber, const uint8_t* src) {
|
|
||||||
#if SD_PROTECT_BLOCK_ZERO
|
|
||||||
// don't allow write to first block
|
|
||||||
if (blockNumber == 0) {
|
|
||||||
error(SD_CARD_ERROR_WRITE_BLOCK_ZERO);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
#endif // SD_PROTECT_BLOCK_ZERO
|
|
||||||
|
|
||||||
// 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 */
|
|
||||||
uint8_t Sd2Card::writeData(const uint8_t* src) {
|
|
||||||
// wait for previous write to finish
|
|
||||||
if (!waitNotBusy(SD_WRITE_TIMEOUT)) {
|
|
||||||
error(SD_CARD_ERROR_WRITE_MULTIPLE);
|
|
||||||
chipSelectHigh();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return writeData(WRITE_MULTIPLE_TOKEN, src);
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// send one block of data for write block or write multiple blocks
|
|
||||||
uint8_t Sd2Card::writeData(uint8_t token, const uint8_t* src) {
|
|
||||||
#ifdef OPTIMIZE_HARDWARE_SPI
|
|
||||||
|
|
||||||
// send data - optimized loop
|
|
||||||
SPDR = token;
|
|
||||||
|
|
||||||
// send two byte per iteration
|
|
||||||
for (uint16_t i = 0; i < 512; i += 2) {
|
|
||||||
while (!(SPSR & (1 << SPIF)))
|
|
||||||
;
|
|
||||||
SPDR = src[i];
|
|
||||||
while (!(SPSR & (1 << SPIF)))
|
|
||||||
;
|
|
||||||
SPDR = src[i+1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for last data byte
|
|
||||||
while (!(SPSR & (1 << SPIF)))
|
|
||||||
;
|
|
||||||
|
|
||||||
#else // OPTIMIZE_HARDWARE_SPI
|
|
||||||
spiSend(token);
|
|
||||||
#ifdef ESP8266
|
|
||||||
// send argument
|
|
||||||
SPI.writeBytes((uint8_t *)src, 512);
|
|
||||||
#else
|
|
||||||
for (uint16_t i = 0; i < 512; i++) {
|
|
||||||
spiSend(src[i]);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif // OPTIMIZE_HARDWARE_SPI
|
|
||||||
#ifdef ESP8266
|
|
||||||
SPI.write16(0xFFFF, true);
|
|
||||||
#else
|
|
||||||
spiSend(0xff); // dummy crc
|
|
||||||
spiSend(0xff); // dummy crc
|
|
||||||
#endif
|
|
||||||
status_ = spiRec();
|
|
||||||
if ((status_ & DATA_RES_MASK) != DATA_RES_ACCEPTED) {
|
|
||||||
error(SD_CARD_ERROR_WRITE);
|
|
||||||
chipSelectHigh();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/** 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.
|
|
||||||
*/
|
|
||||||
uint8_t Sd2Card::writeStart(uint32_t blockNumber, uint32_t eraseCount) {
|
|
||||||
#if SD_PROTECT_BLOCK_ZERO
|
|
||||||
// don't allow write to first block
|
|
||||||
if (blockNumber == 0) {
|
|
||||||
error(SD_CARD_ERROR_WRITE_BLOCK_ZERO);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
#endif // SD_PROTECT_BLOCK_ZERO
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
uint8_t Sd2Card::writeStop(void) {
|
|
||||||
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;
|
|
||||||
}
|
|
@ -1,262 +0,0 @@
|
|||||||
/* Arduino Sd2Card Library
|
|
||||||
* Copyright (C) 2009 by William Greiman
|
|
||||||
*
|
|
||||||
* This file is part of the Arduino Sd2Card 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 Sd2Card Library. If not, see
|
|
||||||
* <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#ifndef Sd2Card_h
|
|
||||||
#define Sd2Card_h
|
|
||||||
/**
|
|
||||||
* \file
|
|
||||||
* Sd2Card class
|
|
||||||
*/
|
|
||||||
#include "Sd2PinMap.h"
|
|
||||||
#include "SdInfo.h"
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
#include "SPI.h"
|
|
||||||
uint32_t const SPI_FULL_SPEED = 8000000;
|
|
||||||
uint32_t const SPI_HALF_SPEED = 4000000;
|
|
||||||
uint32_t const SPI_QUARTER_SPEED = 2000000;
|
|
||||||
#else
|
|
||||||
/** Set SCK to max rate of F_CPU/2. See Sd2Card::setSckRate(). */
|
|
||||||
uint8_t const SPI_FULL_SPEED = 0;
|
|
||||||
/** Set SCK rate to F_CPU/4. See Sd2Card::setSckRate(). */
|
|
||||||
uint8_t const SPI_HALF_SPEED = 1;
|
|
||||||
/** Set SCK rate to F_CPU/8. Sd2Card::setSckRate(). */
|
|
||||||
uint8_t const SPI_QUARTER_SPEED = 2;
|
|
||||||
#endif
|
|
||||||
/**
|
|
||||||
* USE_SPI_LIB: if set, use the SPI library bundled with Arduino IDE, otherwise
|
|
||||||
* run with a standalone driver for AVR.
|
|
||||||
*/
|
|
||||||
#define USE_SPI_LIB
|
|
||||||
/**
|
|
||||||
* Define MEGA_SOFT_SPI non-zero to use software SPI on Mega Arduinos.
|
|
||||||
* Pins used are SS 10, MOSI 11, MISO 12, and SCK 13.
|
|
||||||
*
|
|
||||||
* MEGA_SOFT_SPI allows an unmodified Adafruit GPS Shield to be used
|
|
||||||
* on Mega Arduinos. Software SPI works well with GPS Shield V1.1
|
|
||||||
* but many SD cards will fail with GPS Shield V1.0.
|
|
||||||
*/
|
|
||||||
#define MEGA_SOFT_SPI 0
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
#if MEGA_SOFT_SPI && (defined(__AVR_ATmega1280__)||defined(__AVR_ATmega2560__))
|
|
||||||
#define SOFTWARE_SPI
|
|
||||||
#endif // MEGA_SOFT_SPI
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// SPI pin definitions
|
|
||||||
//
|
|
||||||
#ifndef SOFTWARE_SPI
|
|
||||||
// hardware pin defs
|
|
||||||
/**
|
|
||||||
* SD Chip Select pin
|
|
||||||
*
|
|
||||||
* Warning if this pin is redefined the hardware SS will pin will be enabled
|
|
||||||
* as an output by init(). An avr processor will not function as an SPI
|
|
||||||
* master unless SS is set to output mode.
|
|
||||||
*/
|
|
||||||
/** The default chip select pin for the SD card is SS. */
|
|
||||||
uint8_t const SD_CHIP_SELECT_PIN = SS_PIN;
|
|
||||||
// The following three pins must not be redefined for hardware SPI.
|
|
||||||
/** SPI Master Out Slave In pin */
|
|
||||||
uint8_t const SPI_MOSI_PIN = MOSI_PIN;
|
|
||||||
/** SPI Master In Slave Out pin */
|
|
||||||
uint8_t const SPI_MISO_PIN = MISO_PIN;
|
|
||||||
/** SPI Clock pin */
|
|
||||||
uint8_t const SPI_SCK_PIN = SCK_PIN;
|
|
||||||
/** optimize loops for hardware SPI */
|
|
||||||
#ifndef USE_SPI_LIB
|
|
||||||
#define OPTIMIZE_HARDWARE_SPI
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#else // SOFTWARE_SPI
|
|
||||||
// define software SPI pins so Mega can use unmodified GPS Shield
|
|
||||||
/** SPI chip select pin */
|
|
||||||
uint8_t const SD_CHIP_SELECT_PIN = 10;
|
|
||||||
/** SPI Master Out Slave In pin */
|
|
||||||
uint8_t const SPI_MOSI_PIN = 11;
|
|
||||||
/** SPI Master In Slave Out pin */
|
|
||||||
uint8_t const SPI_MISO_PIN = 12;
|
|
||||||
/** SPI Clock pin */
|
|
||||||
uint8_t const SPI_SCK_PIN = 13;
|
|
||||||
#endif // SOFTWARE_SPI
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/** Protect block zero from write if nonzero */
|
|
||||||
#define SD_PROTECT_BLOCK_ZERO 1
|
|
||||||
/** init timeout ms */
|
|
||||||
uint16_t const SD_INIT_TIMEOUT = 2000;
|
|
||||||
/** erase timeout ms */
|
|
||||||
uint16_t const SD_ERASE_TIMEOUT = 10000;
|
|
||||||
/** read timeout ms */
|
|
||||||
uint16_t const SD_READ_TIMEOUT = 300;
|
|
||||||
/** write time out ms */
|
|
||||||
uint16_t const SD_WRITE_TIMEOUT = 600;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// SD card errors
|
|
||||||
/** timeout error for command CMD0 */
|
|
||||||
uint8_t const SD_CARD_ERROR_CMD0 = 0X1;
|
|
||||||
/** CMD8 was not accepted - not a valid SD card*/
|
|
||||||
uint8_t const SD_CARD_ERROR_CMD8 = 0X2;
|
|
||||||
/** card returned an error response for CMD17 (read block) */
|
|
||||||
uint8_t const SD_CARD_ERROR_CMD17 = 0X3;
|
|
||||||
/** card returned an error response for CMD24 (write block) */
|
|
||||||
uint8_t const SD_CARD_ERROR_CMD24 = 0X4;
|
|
||||||
/** WRITE_MULTIPLE_BLOCKS command failed */
|
|
||||||
uint8_t const SD_CARD_ERROR_CMD25 = 0X05;
|
|
||||||
/** card returned an error response for CMD58 (read OCR) */
|
|
||||||
uint8_t const SD_CARD_ERROR_CMD58 = 0X06;
|
|
||||||
/** SET_WR_BLK_ERASE_COUNT failed */
|
|
||||||
uint8_t const SD_CARD_ERROR_ACMD23 = 0X07;
|
|
||||||
/** card's ACMD41 initialization process timeout */
|
|
||||||
uint8_t const SD_CARD_ERROR_ACMD41 = 0X08;
|
|
||||||
/** card returned a bad CSR version field */
|
|
||||||
uint8_t const SD_CARD_ERROR_BAD_CSD = 0X09;
|
|
||||||
/** erase block group command failed */
|
|
||||||
uint8_t const SD_CARD_ERROR_ERASE = 0X0A;
|
|
||||||
/** card not capable of single block erase */
|
|
||||||
uint8_t const SD_CARD_ERROR_ERASE_SINGLE_BLOCK = 0X0B;
|
|
||||||
/** Erase sequence timed out */
|
|
||||||
uint8_t const SD_CARD_ERROR_ERASE_TIMEOUT = 0X0C;
|
|
||||||
/** card returned an error token instead of read data */
|
|
||||||
uint8_t const SD_CARD_ERROR_READ = 0X0D;
|
|
||||||
/** read CID or CSD failed */
|
|
||||||
uint8_t const SD_CARD_ERROR_READ_REG = 0X0E;
|
|
||||||
/** timeout while waiting for start of read data */
|
|
||||||
uint8_t const SD_CARD_ERROR_READ_TIMEOUT = 0X0F;
|
|
||||||
/** card did not accept STOP_TRAN_TOKEN */
|
|
||||||
uint8_t const SD_CARD_ERROR_STOP_TRAN = 0X10;
|
|
||||||
/** card returned an error token as a response to a write operation */
|
|
||||||
uint8_t const SD_CARD_ERROR_WRITE = 0X11;
|
|
||||||
/** attempt to write protected block zero */
|
|
||||||
uint8_t const SD_CARD_ERROR_WRITE_BLOCK_ZERO = 0X12;
|
|
||||||
/** card did not go ready for a multiple block write */
|
|
||||||
uint8_t const SD_CARD_ERROR_WRITE_MULTIPLE = 0X13;
|
|
||||||
/** card returned an error to a CMD13 status check after a write */
|
|
||||||
uint8_t const SD_CARD_ERROR_WRITE_PROGRAMMING = 0X14;
|
|
||||||
/** timeout occurred during write programming */
|
|
||||||
uint8_t const SD_CARD_ERROR_WRITE_TIMEOUT = 0X15;
|
|
||||||
/** incorrect rate selected */
|
|
||||||
uint8_t const SD_CARD_ERROR_SCK_RATE = 0X16;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// card types
|
|
||||||
/** Standard capacity V1 SD card */
|
|
||||||
uint8_t const SD_CARD_TYPE_SD1 = 1;
|
|
||||||
/** Standard capacity V2 SD card */
|
|
||||||
uint8_t const SD_CARD_TYPE_SD2 = 2;
|
|
||||||
/** High Capacity SD card */
|
|
||||||
uint8_t const SD_CARD_TYPE_SDHC = 3;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* \class Sd2Card
|
|
||||||
* \brief Raw access to SD and SDHC flash memory cards.
|
|
||||||
*/
|
|
||||||
class Sd2Card {
|
|
||||||
public:
|
|
||||||
/** Construct an instance of Sd2Card. */
|
|
||||||
Sd2Card(void) : errorCode_(0), inBlock_(0), partialBlockRead_(0), type_(0) {}
|
|
||||||
uint32_t cardSize(void);
|
|
||||||
uint8_t erase(uint32_t firstBlock, uint32_t lastBlock);
|
|
||||||
uint8_t eraseSingleBlockEnable(void);
|
|
||||||
/**
|
|
||||||
* \return error code for last error. See Sd2Card.h for a list of error codes.
|
|
||||||
*/
|
|
||||||
uint8_t errorCode(void) const {return errorCode_;}
|
|
||||||
/** \return error data for last error. */
|
|
||||||
uint8_t errorData(void) const {return status_;}
|
|
||||||
/**
|
|
||||||
* Initialize an SD flash memory card with default clock rate and chip
|
|
||||||
* select pin. See sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin).
|
|
||||||
*/
|
|
||||||
uint8_t init(void) {
|
|
||||||
return init(SPI_FULL_SPEED, SD_CHIP_SELECT_PIN);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Initialize an SD flash memory card with the selected SPI clock rate
|
|
||||||
* and the default SD chip select pin.
|
|
||||||
* See sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin).
|
|
||||||
*/
|
|
||||||
#ifdef ESP8266
|
|
||||||
uint8_t init(uint32_t sckRateID) {
|
|
||||||
return init(sckRateID, SD_CHIP_SELECT_PIN);
|
|
||||||
}
|
|
||||||
uint8_t init(uint32_t sckRateID, uint8_t chipSelectPin);
|
|
||||||
#else
|
|
||||||
uint8_t init(uint8_t sckRateID) {
|
|
||||||
return init(sckRateID, SD_CHIP_SELECT_PIN);
|
|
||||||
}
|
|
||||||
uint8_t init(uint8_t sckRateID, uint8_t chipSelectPin);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void end(bool endSPI = true);
|
|
||||||
|
|
||||||
void partialBlockRead(uint8_t value);
|
|
||||||
/** Returns the current value, true or false, for partial block read. */
|
|
||||||
uint8_t partialBlockRead(void) const {return partialBlockRead_;}
|
|
||||||
uint8_t readBlock(uint32_t block, uint8_t* dst);
|
|
||||||
uint8_t readData(uint32_t block,
|
|
||||||
uint16_t offset, uint16_t count, uint8_t* dst);
|
|
||||||
/**
|
|
||||||
* Read a cards CID register. The CID contains card identification
|
|
||||||
* information such as Manufacturer ID, Product name, Product serial
|
|
||||||
* number and Manufacturing date. */
|
|
||||||
uint8_t readCID(cid_t* cid) {
|
|
||||||
return readRegister(CMD10, cid);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Read a cards CSD register. The CSD contains Card-Specific Data that
|
|
||||||
* provides information regarding access to the card's contents. */
|
|
||||||
uint8_t readCSD(csd_t* csd) {
|
|
||||||
return readRegister(CMD9, csd);
|
|
||||||
}
|
|
||||||
void readEnd(void);
|
|
||||||
#ifdef ESP8266
|
|
||||||
uint8_t setSckRate(uint32_t sckRateID);
|
|
||||||
#else
|
|
||||||
uint8_t setSckRate(uint8_t sckRateID);
|
|
||||||
#endif
|
|
||||||
/** Return the card type: SD V1, SD V2 or SDHC */
|
|
||||||
uint8_t type(void) const {return type_;}
|
|
||||||
uint8_t writeBlock(uint32_t blockNumber, const uint8_t* src);
|
|
||||||
uint8_t writeData(const uint8_t* src);
|
|
||||||
uint8_t writeStart(uint32_t blockNumber, uint32_t eraseCount);
|
|
||||||
uint8_t writeStop(void);
|
|
||||||
private:
|
|
||||||
uint32_t block_;
|
|
||||||
uint8_t chipSelectPin_;
|
|
||||||
uint8_t errorCode_;
|
|
||||||
uint8_t inBlock_;
|
|
||||||
uint16_t offset_;
|
|
||||||
uint8_t partialBlockRead_;
|
|
||||||
uint8_t status_;
|
|
||||||
uint8_t type_;
|
|
||||||
// private functions
|
|
||||||
uint8_t cardAcmd(uint8_t cmd, uint32_t arg) {
|
|
||||||
cardCommand(CMD55, 0);
|
|
||||||
return cardCommand(cmd, arg);
|
|
||||||
}
|
|
||||||
uint8_t cardCommand(uint8_t cmd, uint32_t arg);
|
|
||||||
void error(uint8_t code) {errorCode_ = code;}
|
|
||||||
uint8_t readRegister(uint8_t cmd, void* buf);
|
|
||||||
uint8_t sendWriteCommand(uint32_t blockNumber, uint32_t eraseCount);
|
|
||||||
void chipSelectHigh(void);
|
|
||||||
void chipSelectLow(void);
|
|
||||||
void type(uint8_t value) {type_ = value;}
|
|
||||||
uint8_t waitNotBusy(uint16_t timeoutMillis);
|
|
||||||
uint8_t writeData(uint8_t token, const uint8_t* src);
|
|
||||||
uint8_t waitStartBlock(void);
|
|
||||||
};
|
|
||||||
#endif // Sd2Card_h
|
|
@ -1,385 +0,0 @@
|
|||||||
/* Arduino SdFat Library
|
|
||||||
* Copyright (C) 2010 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/>.
|
|
||||||
*/
|
|
||||||
#if defined(__arm__) // Arduino Due Board follows
|
|
||||||
|
|
||||||
#ifndef Sd2PinMap_h
|
|
||||||
#define Sd2PinMap_h
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
uint8_t const SS_PIN = SS;
|
|
||||||
uint8_t const MOSI_PIN = MOSI;
|
|
||||||
uint8_t const MISO_PIN = MISO;
|
|
||||||
uint8_t const SCK_PIN = SCK;
|
|
||||||
|
|
||||||
#endif // Sd2PinMap_h
|
|
||||||
|
|
||||||
#elif defined(ESP8266) // Other AVR based Boards follows
|
|
||||||
#ifndef Sd2PinMap_h
|
|
||||||
#define Sd2PinMap_h
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
uint8_t const SS_PIN = SS;
|
|
||||||
uint8_t const MOSI_PIN = MOSI;
|
|
||||||
uint8_t const MISO_PIN = MISO;
|
|
||||||
uint8_t const SCK_PIN = SCK;
|
|
||||||
|
|
||||||
#endif // Sd2PinMap_h
|
|
||||||
|
|
||||||
#elif defined(__AVR__) // Other AVR based Boards follows
|
|
||||||
|
|
||||||
// Warning this file was generated by a program.
|
|
||||||
#ifndef Sd2PinMap_h
|
|
||||||
#define Sd2PinMap_h
|
|
||||||
#include <avr/io.h>
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/** struct for mapping digital pins */
|
|
||||||
struct pin_map_t {
|
|
||||||
volatile uint8_t* ddr;
|
|
||||||
volatile uint8_t* pin;
|
|
||||||
volatile uint8_t* port;
|
|
||||||
uint8_t bit;
|
|
||||||
};
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
|
||||||
// Mega
|
|
||||||
|
|
||||||
// Two Wire (aka I2C) ports
|
|
||||||
uint8_t const SDA_PIN = 20;
|
|
||||||
uint8_t const SCL_PIN = 21;
|
|
||||||
|
|
||||||
// SPI port
|
|
||||||
uint8_t const SS_PIN = 53;
|
|
||||||
uint8_t const MOSI_PIN = 51;
|
|
||||||
uint8_t const MISO_PIN = 50;
|
|
||||||
uint8_t const SCK_PIN = 52;
|
|
||||||
|
|
||||||
static const pin_map_t digitalPinMap[] = {
|
|
||||||
{&DDRE, &PINE, &PORTE, 0}, // E0 0
|
|
||||||
{&DDRE, &PINE, &PORTE, 1}, // E1 1
|
|
||||||
{&DDRE, &PINE, &PORTE, 4}, // E4 2
|
|
||||||
{&DDRE, &PINE, &PORTE, 5}, // E5 3
|
|
||||||
{&DDRG, &PING, &PORTG, 5}, // G5 4
|
|
||||||
{&DDRE, &PINE, &PORTE, 3}, // E3 5
|
|
||||||
{&DDRH, &PINH, &PORTH, 3}, // H3 6
|
|
||||||
{&DDRH, &PINH, &PORTH, 4}, // H4 7
|
|
||||||
{&DDRH, &PINH, &PORTH, 5}, // H5 8
|
|
||||||
{&DDRH, &PINH, &PORTH, 6}, // H6 9
|
|
||||||
{&DDRB, &PINB, &PORTB, 4}, // B4 10
|
|
||||||
{&DDRB, &PINB, &PORTB, 5}, // B5 11
|
|
||||||
{&DDRB, &PINB, &PORTB, 6}, // B6 12
|
|
||||||
{&DDRB, &PINB, &PORTB, 7}, // B7 13
|
|
||||||
{&DDRJ, &PINJ, &PORTJ, 1}, // J1 14
|
|
||||||
{&DDRJ, &PINJ, &PORTJ, 0}, // J0 15
|
|
||||||
{&DDRH, &PINH, &PORTH, 1}, // H1 16
|
|
||||||
{&DDRH, &PINH, &PORTH, 0}, // H0 17
|
|
||||||
{&DDRD, &PIND, &PORTD, 3}, // D3 18
|
|
||||||
{&DDRD, &PIND, &PORTD, 2}, // D2 19
|
|
||||||
{&DDRD, &PIND, &PORTD, 1}, // D1 20
|
|
||||||
{&DDRD, &PIND, &PORTD, 0}, // D0 21
|
|
||||||
{&DDRA, &PINA, &PORTA, 0}, // A0 22
|
|
||||||
{&DDRA, &PINA, &PORTA, 1}, // A1 23
|
|
||||||
{&DDRA, &PINA, &PORTA, 2}, // A2 24
|
|
||||||
{&DDRA, &PINA, &PORTA, 3}, // A3 25
|
|
||||||
{&DDRA, &PINA, &PORTA, 4}, // A4 26
|
|
||||||
{&DDRA, &PINA, &PORTA, 5}, // A5 27
|
|
||||||
{&DDRA, &PINA, &PORTA, 6}, // A6 28
|
|
||||||
{&DDRA, &PINA, &PORTA, 7}, // A7 29
|
|
||||||
{&DDRC, &PINC, &PORTC, 7}, // C7 30
|
|
||||||
{&DDRC, &PINC, &PORTC, 6}, // C6 31
|
|
||||||
{&DDRC, &PINC, &PORTC, 5}, // C5 32
|
|
||||||
{&DDRC, &PINC, &PORTC, 4}, // C4 33
|
|
||||||
{&DDRC, &PINC, &PORTC, 3}, // C3 34
|
|
||||||
{&DDRC, &PINC, &PORTC, 2}, // C2 35
|
|
||||||
{&DDRC, &PINC, &PORTC, 1}, // C1 36
|
|
||||||
{&DDRC, &PINC, &PORTC, 0}, // C0 37
|
|
||||||
{&DDRD, &PIND, &PORTD, 7}, // D7 38
|
|
||||||
{&DDRG, &PING, &PORTG, 2}, // G2 39
|
|
||||||
{&DDRG, &PING, &PORTG, 1}, // G1 40
|
|
||||||
{&DDRG, &PING, &PORTG, 0}, // G0 41
|
|
||||||
{&DDRL, &PINL, &PORTL, 7}, // L7 42
|
|
||||||
{&DDRL, &PINL, &PORTL, 6}, // L6 43
|
|
||||||
{&DDRL, &PINL, &PORTL, 5}, // L5 44
|
|
||||||
{&DDRL, &PINL, &PORTL, 4}, // L4 45
|
|
||||||
{&DDRL, &PINL, &PORTL, 3}, // L3 46
|
|
||||||
{&DDRL, &PINL, &PORTL, 2}, // L2 47
|
|
||||||
{&DDRL, &PINL, &PORTL, 1}, // L1 48
|
|
||||||
{&DDRL, &PINL, &PORTL, 0}, // L0 49
|
|
||||||
{&DDRB, &PINB, &PORTB, 3}, // B3 50
|
|
||||||
{&DDRB, &PINB, &PORTB, 2}, // B2 51
|
|
||||||
{&DDRB, &PINB, &PORTB, 1}, // B1 52
|
|
||||||
{&DDRB, &PINB, &PORTB, 0}, // B0 53
|
|
||||||
{&DDRF, &PINF, &PORTF, 0}, // F0 54
|
|
||||||
{&DDRF, &PINF, &PORTF, 1}, // F1 55
|
|
||||||
{&DDRF, &PINF, &PORTF, 2}, // F2 56
|
|
||||||
{&DDRF, &PINF, &PORTF, 3}, // F3 57
|
|
||||||
{&DDRF, &PINF, &PORTF, 4}, // F4 58
|
|
||||||
{&DDRF, &PINF, &PORTF, 5}, // F5 59
|
|
||||||
{&DDRF, &PINF, &PORTF, 6}, // F6 60
|
|
||||||
{&DDRF, &PINF, &PORTF, 7}, // F7 61
|
|
||||||
{&DDRK, &PINK, &PORTK, 0}, // K0 62
|
|
||||||
{&DDRK, &PINK, &PORTK, 1}, // K1 63
|
|
||||||
{&DDRK, &PINK, &PORTK, 2}, // K2 64
|
|
||||||
{&DDRK, &PINK, &PORTK, 3}, // K3 65
|
|
||||||
{&DDRK, &PINK, &PORTK, 4}, // K4 66
|
|
||||||
{&DDRK, &PINK, &PORTK, 5}, // K5 67
|
|
||||||
{&DDRK, &PINK, &PORTK, 6}, // K6 68
|
|
||||||
{&DDRK, &PINK, &PORTK, 7} // K7 69
|
|
||||||
};
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
#elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__)
|
|
||||||
// Sanguino
|
|
||||||
|
|
||||||
// Two Wire (aka I2C) ports
|
|
||||||
uint8_t const SDA_PIN = 17;
|
|
||||||
uint8_t const SCL_PIN = 18;
|
|
||||||
|
|
||||||
// SPI port
|
|
||||||
uint8_t const SS_PIN = 4;
|
|
||||||
uint8_t const MOSI_PIN = 5;
|
|
||||||
uint8_t const MISO_PIN = 6;
|
|
||||||
uint8_t const SCK_PIN = 7;
|
|
||||||
|
|
||||||
static const pin_map_t digitalPinMap[] = {
|
|
||||||
{&DDRB, &PINB, &PORTB, 0}, // B0 0
|
|
||||||
{&DDRB, &PINB, &PORTB, 1}, // B1 1
|
|
||||||
{&DDRB, &PINB, &PORTB, 2}, // B2 2
|
|
||||||
{&DDRB, &PINB, &PORTB, 3}, // B3 3
|
|
||||||
{&DDRB, &PINB, &PORTB, 4}, // B4 4
|
|
||||||
{&DDRB, &PINB, &PORTB, 5}, // B5 5
|
|
||||||
{&DDRB, &PINB, &PORTB, 6}, // B6 6
|
|
||||||
{&DDRB, &PINB, &PORTB, 7}, // B7 7
|
|
||||||
{&DDRD, &PIND, &PORTD, 0}, // D0 8
|
|
||||||
{&DDRD, &PIND, &PORTD, 1}, // D1 9
|
|
||||||
{&DDRD, &PIND, &PORTD, 2}, // D2 10
|
|
||||||
{&DDRD, &PIND, &PORTD, 3}, // D3 11
|
|
||||||
{&DDRD, &PIND, &PORTD, 4}, // D4 12
|
|
||||||
{&DDRD, &PIND, &PORTD, 5}, // D5 13
|
|
||||||
{&DDRD, &PIND, &PORTD, 6}, // D6 14
|
|
||||||
{&DDRD, &PIND, &PORTD, 7}, // D7 15
|
|
||||||
{&DDRC, &PINC, &PORTC, 0}, // C0 16
|
|
||||||
{&DDRC, &PINC, &PORTC, 1}, // C1 17
|
|
||||||
{&DDRC, &PINC, &PORTC, 2}, // C2 18
|
|
||||||
{&DDRC, &PINC, &PORTC, 3}, // C3 19
|
|
||||||
{&DDRC, &PINC, &PORTC, 4}, // C4 20
|
|
||||||
{&DDRC, &PINC, &PORTC, 5}, // C5 21
|
|
||||||
{&DDRC, &PINC, &PORTC, 6}, // C6 22
|
|
||||||
{&DDRC, &PINC, &PORTC, 7}, // C7 23
|
|
||||||
{&DDRA, &PINA, &PORTA, 7}, // A7 24
|
|
||||||
{&DDRA, &PINA, &PORTA, 6}, // A6 25
|
|
||||||
{&DDRA, &PINA, &PORTA, 5}, // A5 26
|
|
||||||
{&DDRA, &PINA, &PORTA, 4}, // A4 27
|
|
||||||
{&DDRA, &PINA, &PORTA, 3}, // A3 28
|
|
||||||
{&DDRA, &PINA, &PORTA, 2}, // A2 29
|
|
||||||
{&DDRA, &PINA, &PORTA, 1}, // A1 30
|
|
||||||
{&DDRA, &PINA, &PORTA, 0} // A0 31
|
|
||||||
};
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
#elif defined(__AVR_ATmega32U4__)
|
|
||||||
// Leonardo
|
|
||||||
|
|
||||||
// Two Wire (aka I2C) ports
|
|
||||||
uint8_t const SDA_PIN = 2;
|
|
||||||
uint8_t const SCL_PIN = 3;
|
|
||||||
|
|
||||||
// SPI port
|
|
||||||
uint8_t const SS_PIN = 17;
|
|
||||||
uint8_t const MOSI_PIN = 16;
|
|
||||||
uint8_t const MISO_PIN = 14;
|
|
||||||
uint8_t const SCK_PIN = 15;
|
|
||||||
|
|
||||||
static const pin_map_t digitalPinMap[] = {
|
|
||||||
{&DDRD, &PIND, &PORTD, 2}, // D2 0
|
|
||||||
{&DDRD, &PIND, &PORTD, 3}, // D3 1
|
|
||||||
{&DDRD, &PIND, &PORTD, 1}, // D1 2
|
|
||||||
{&DDRD, &PIND, &PORTD, 0}, // D0 3
|
|
||||||
{&DDRD, &PIND, &PORTD, 4}, // D4 4
|
|
||||||
{&DDRC, &PINC, &PORTC, 6}, // C6 5
|
|
||||||
{&DDRD, &PIND, &PORTD, 7}, // D7 6
|
|
||||||
{&DDRE, &PINE, &PORTE, 6}, // E6 7
|
|
||||||
{&DDRB, &PINB, &PORTB, 4}, // B4 8
|
|
||||||
{&DDRB, &PINB, &PORTB, 5}, // B5 9
|
|
||||||
{&DDRB, &PINB, &PORTB, 6}, // B6 10
|
|
||||||
{&DDRB, &PINB, &PORTB, 7}, // B7 11
|
|
||||||
{&DDRD, &PIND, &PORTD, 6}, // D6 12
|
|
||||||
{&DDRC, &PINC, &PORTC, 7}, // C7 13
|
|
||||||
{&DDRB, &PINB, &PORTB, 3}, // B3 14
|
|
||||||
{&DDRB, &PINB, &PORTB, 1}, // B1 15
|
|
||||||
{&DDRB, &PINB, &PORTB, 2}, // B2 16
|
|
||||||
{&DDRB, &PINB, &PORTB, 0}, // B0 17
|
|
||||||
{&DDRF, &PINF, &PORTF, 7}, // F7 18
|
|
||||||
{&DDRF, &PINF, &PORTF, 6}, // F6 19
|
|
||||||
{&DDRF, &PINF, &PORTF, 5}, // F5 20
|
|
||||||
{&DDRF, &PINF, &PORTF, 4}, // F4 21
|
|
||||||
{&DDRF, &PINF, &PORTF, 1}, // F1 22
|
|
||||||
{&DDRF, &PINF, &PORTF, 0}, // F0 23
|
|
||||||
};
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)
|
|
||||||
// Teensy++ 1.0 & 2.0
|
|
||||||
|
|
||||||
// Two Wire (aka I2C) ports
|
|
||||||
uint8_t const SDA_PIN = 1;
|
|
||||||
uint8_t const SCL_PIN = 0;
|
|
||||||
|
|
||||||
// SPI port
|
|
||||||
uint8_t const SS_PIN = 20;
|
|
||||||
uint8_t const MOSI_PIN = 22;
|
|
||||||
uint8_t const MISO_PIN = 23;
|
|
||||||
uint8_t const SCK_PIN = 21;
|
|
||||||
|
|
||||||
static const pin_map_t digitalPinMap[] = {
|
|
||||||
{&DDRD, &PIND, &PORTD, 0}, // D0 0
|
|
||||||
{&DDRD, &PIND, &PORTD, 1}, // D1 1
|
|
||||||
{&DDRD, &PIND, &PORTD, 2}, // D2 2
|
|
||||||
{&DDRD, &PIND, &PORTD, 3}, // D3 3
|
|
||||||
{&DDRD, &PIND, &PORTD, 4}, // D4 4
|
|
||||||
{&DDRD, &PIND, &PORTD, 5}, // D5 5
|
|
||||||
{&DDRD, &PIND, &PORTD, 6}, // D6 6
|
|
||||||
{&DDRD, &PIND, &PORTD, 7}, // D7 7
|
|
||||||
{&DDRE, &PINE, &PORTE, 0}, // E0 8
|
|
||||||
{&DDRE, &PINE, &PORTE, 1}, // E1 9
|
|
||||||
{&DDRC, &PINC, &PORTC, 0}, // C0 10
|
|
||||||
{&DDRC, &PINC, &PORTC, 1}, // C1 11
|
|
||||||
{&DDRC, &PINC, &PORTC, 2}, // C2 12
|
|
||||||
{&DDRC, &PINC, &PORTC, 3}, // C3 13
|
|
||||||
{&DDRC, &PINC, &PORTC, 4}, // C4 14
|
|
||||||
{&DDRC, &PINC, &PORTC, 5}, // C5 15
|
|
||||||
{&DDRC, &PINC, &PORTC, 6}, // C6 16
|
|
||||||
{&DDRC, &PINC, &PORTC, 7}, // C7 17
|
|
||||||
{&DDRE, &PINE, &PORTE, 6}, // E6 18
|
|
||||||
{&DDRE, &PINE, &PORTE, 7}, // E7 19
|
|
||||||
{&DDRB, &PINB, &PORTB, 0}, // B0 20
|
|
||||||
{&DDRB, &PINB, &PORTB, 1}, // B1 21
|
|
||||||
{&DDRB, &PINB, &PORTB, 2}, // B2 22
|
|
||||||
{&DDRB, &PINB, &PORTB, 3}, // B3 23
|
|
||||||
{&DDRB, &PINB, &PORTB, 4}, // B4 24
|
|
||||||
{&DDRB, &PINB, &PORTB, 5}, // B5 25
|
|
||||||
{&DDRB, &PINB, &PORTB, 6}, // B6 26
|
|
||||||
{&DDRB, &PINB, &PORTB, 7}, // B7 27
|
|
||||||
{&DDRA, &PINA, &PORTA, 0}, // A0 28
|
|
||||||
{&DDRA, &PINA, &PORTA, 1}, // A1 29
|
|
||||||
{&DDRA, &PINA, &PORTA, 2}, // A2 30
|
|
||||||
{&DDRA, &PINA, &PORTA, 3}, // A3 31
|
|
||||||
{&DDRA, &PINA, &PORTA, 4}, // A4 32
|
|
||||||
{&DDRA, &PINA, &PORTA, 5}, // A5 33
|
|
||||||
{&DDRA, &PINA, &PORTA, 6}, // A6 34
|
|
||||||
{&DDRA, &PINA, &PORTA, 7}, // A7 35
|
|
||||||
{&DDRE, &PINE, &PORTE, 4}, // E4 36
|
|
||||||
{&DDRE, &PINE, &PORTE, 5}, // E5 37
|
|
||||||
{&DDRF, &PINF, &PORTF, 0}, // F0 38
|
|
||||||
{&DDRF, &PINF, &PORTF, 1}, // F1 39
|
|
||||||
{&DDRF, &PINF, &PORTF, 2}, // F2 40
|
|
||||||
{&DDRF, &PINF, &PORTF, 3}, // F3 41
|
|
||||||
{&DDRF, &PINF, &PORTF, 4}, // F4 42
|
|
||||||
{&DDRF, &PINF, &PORTF, 5}, // F5 43
|
|
||||||
{&DDRF, &PINF, &PORTF, 6}, // F6 44
|
|
||||||
{&DDRF, &PINF, &PORTF, 7} // F7 45
|
|
||||||
};
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
#else // defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
|
||||||
// 168 and 328 Arduinos
|
|
||||||
|
|
||||||
// Two Wire (aka I2C) ports
|
|
||||||
uint8_t const SDA_PIN = 18;
|
|
||||||
uint8_t const SCL_PIN = 19;
|
|
||||||
|
|
||||||
// SPI port
|
|
||||||
uint8_t const SS_PIN = 10;
|
|
||||||
uint8_t const MOSI_PIN = 11;
|
|
||||||
uint8_t const MISO_PIN = 12;
|
|
||||||
uint8_t const SCK_PIN = 13;
|
|
||||||
|
|
||||||
static const pin_map_t digitalPinMap[] = {
|
|
||||||
{&DDRD, &PIND, &PORTD, 0}, // D0 0
|
|
||||||
{&DDRD, &PIND, &PORTD, 1}, // D1 1
|
|
||||||
{&DDRD, &PIND, &PORTD, 2}, // D2 2
|
|
||||||
{&DDRD, &PIND, &PORTD, 3}, // D3 3
|
|
||||||
{&DDRD, &PIND, &PORTD, 4}, // D4 4
|
|
||||||
{&DDRD, &PIND, &PORTD, 5}, // D5 5
|
|
||||||
{&DDRD, &PIND, &PORTD, 6}, // D6 6
|
|
||||||
{&DDRD, &PIND, &PORTD, 7}, // D7 7
|
|
||||||
{&DDRB, &PINB, &PORTB, 0}, // B0 8
|
|
||||||
{&DDRB, &PINB, &PORTB, 1}, // B1 9
|
|
||||||
{&DDRB, &PINB, &PORTB, 2}, // B2 10
|
|
||||||
{&DDRB, &PINB, &PORTB, 3}, // B3 11
|
|
||||||
{&DDRB, &PINB, &PORTB, 4}, // B4 12
|
|
||||||
{&DDRB, &PINB, &PORTB, 5}, // B5 13
|
|
||||||
{&DDRC, &PINC, &PORTC, 0}, // C0 14
|
|
||||||
{&DDRC, &PINC, &PORTC, 1}, // C1 15
|
|
||||||
{&DDRC, &PINC, &PORTC, 2}, // C2 16
|
|
||||||
{&DDRC, &PINC, &PORTC, 3}, // C3 17
|
|
||||||
{&DDRC, &PINC, &PORTC, 4}, // C4 18
|
|
||||||
{&DDRC, &PINC, &PORTC, 5} // C5 19
|
|
||||||
};
|
|
||||||
#endif // defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
static const uint8_t digitalPinCount = sizeof(digitalPinMap)/sizeof(pin_map_t);
|
|
||||||
|
|
||||||
uint8_t badPinNumber(void)
|
|
||||||
__attribute__((error("Pin number is too large or not a constant")));
|
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
|
||||||
uint8_t getPinMode(uint8_t pin) {
|
|
||||||
if (__builtin_constant_p(pin) && pin < digitalPinCount) {
|
|
||||||
return (*digitalPinMap[pin].ddr >> digitalPinMap[pin].bit) & 1;
|
|
||||||
} else {
|
|
||||||
return badPinNumber();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static inline __attribute__((always_inline))
|
|
||||||
void setPinMode(uint8_t pin, uint8_t mode) {
|
|
||||||
if (__builtin_constant_p(pin) && pin < digitalPinCount) {
|
|
||||||
if (mode) {
|
|
||||||
*digitalPinMap[pin].ddr |= 1 << digitalPinMap[pin].bit;
|
|
||||||
} else {
|
|
||||||
*digitalPinMap[pin].ddr &= ~(1 << digitalPinMap[pin].bit);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
badPinNumber();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static inline __attribute__((always_inline))
|
|
||||||
uint8_t fastDigitalRead(uint8_t pin) {
|
|
||||||
if (__builtin_constant_p(pin) && pin < digitalPinCount) {
|
|
||||||
return (*digitalPinMap[pin].pin >> digitalPinMap[pin].bit) & 1;
|
|
||||||
} else {
|
|
||||||
return badPinNumber();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static inline __attribute__((always_inline))
|
|
||||||
void fastDigitalWrite(uint8_t pin, uint8_t value) {
|
|
||||||
if (__builtin_constant_p(pin) && pin < digitalPinCount) {
|
|
||||||
if (value) {
|
|
||||||
*digitalPinMap[pin].port |= 1 << digitalPinMap[pin].bit;
|
|
||||||
} else {
|
|
||||||
*digitalPinMap[pin].port &= ~(1 << digitalPinMap[pin].bit);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
badPinNumber();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // Sd2PinMap_h
|
|
||||||
|
|
||||||
#else
|
|
||||||
#error Architecture or board not supported.
|
|
||||||
#endif
|
|
@ -1,551 +0,0 @@
|
|||||||
/* Arduino SdFat Library
|
|
||||||
* Copyright (C) 2009 by William Greiman
|
|
||||||
*
|
|
||||||
* This file is part of the Arduino SdFat Library
|
|
||||||
*
|
|
||||||
* This Library is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This Library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with the Arduino SdFat Library. If not, see
|
|
||||||
* <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#ifndef SdFat_h
|
|
||||||
#define SdFat_h
|
|
||||||
/**
|
|
||||||
* \file
|
|
||||||
* SdFile and SdVolume classes
|
|
||||||
*/
|
|
||||||
#ifdef __AVR__
|
|
||||||
#include <avr/pgmspace.h>
|
|
||||||
#endif
|
|
||||||
#include "Sd2Card.h"
|
|
||||||
#include "FatStructs.h"
|
|
||||||
#include "Print.h"
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* Allow use of deprecated functions if non-zero
|
|
||||||
*/
|
|
||||||
#define ALLOW_DEPRECATED_FUNCTIONS 1
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// forward declaration since SdVolume is used in SdFile
|
|
||||||
class SdVolume;
|
|
||||||
//==============================================================================
|
|
||||||
// SdFile class
|
|
||||||
|
|
||||||
// flags for ls()
|
|
||||||
/** ls() flag to print modify date */
|
|
||||||
uint8_t const LS_DATE = 1;
|
|
||||||
/** ls() flag to print file size */
|
|
||||||
uint8_t const LS_SIZE = 2;
|
|
||||||
/** ls() flag for recursive list of subdirectories */
|
|
||||||
uint8_t const LS_R = 4;
|
|
||||||
|
|
||||||
// use the gnu style oflag in open()
|
|
||||||
/** open() oflag for reading */
|
|
||||||
uint8_t const O_READ = 0X01;
|
|
||||||
/** open() oflag - same as O_READ */
|
|
||||||
uint8_t const O_RDONLY = O_READ;
|
|
||||||
/** open() oflag for write */
|
|
||||||
uint8_t const O_WRITE = 0X02;
|
|
||||||
/** open() oflag - same as O_WRITE */
|
|
||||||
uint8_t const O_WRONLY = O_WRITE;
|
|
||||||
/** open() oflag for reading and writing */
|
|
||||||
uint8_t const O_RDWR = (O_READ | O_WRITE);
|
|
||||||
/** open() oflag mask for access modes */
|
|
||||||
uint8_t const O_ACCMODE = (O_READ | O_WRITE);
|
|
||||||
/** The file offset shall be set to the end of the file prior to each write. */
|
|
||||||
uint8_t const O_APPEND = 0X04;
|
|
||||||
/** synchronous writes - call sync() after each write */
|
|
||||||
uint8_t const O_SYNC = 0X08;
|
|
||||||
/** create the file if nonexistent */
|
|
||||||
uint8_t const O_CREAT = 0X10;
|
|
||||||
/** If O_CREAT and O_EXCL are set, open() shall fail if the file exists */
|
|
||||||
uint8_t const O_EXCL = 0X20;
|
|
||||||
/** truncate the file to zero length */
|
|
||||||
uint8_t const O_TRUNC = 0X40;
|
|
||||||
|
|
||||||
// flags for timestamp
|
|
||||||
/** set the file's last access date */
|
|
||||||
uint8_t const T_ACCESS = 1;
|
|
||||||
/** set the file's creation date and time */
|
|
||||||
uint8_t const T_CREATE = 2;
|
|
||||||
/** Set the file's write date and time */
|
|
||||||
uint8_t const T_WRITE = 4;
|
|
||||||
// values for type_
|
|
||||||
/** This SdFile has not been opened. */
|
|
||||||
uint8_t const FAT_FILE_TYPE_CLOSED = 0;
|
|
||||||
/** SdFile for a file */
|
|
||||||
uint8_t const FAT_FILE_TYPE_NORMAL = 1;
|
|
||||||
/** SdFile for a FAT16 root directory */
|
|
||||||
uint8_t const FAT_FILE_TYPE_ROOT16 = 2;
|
|
||||||
/** SdFile for a FAT32 root directory */
|
|
||||||
uint8_t const FAT_FILE_TYPE_ROOT32 = 3;
|
|
||||||
/** SdFile for a subdirectory */
|
|
||||||
uint8_t const FAT_FILE_TYPE_SUBDIR = 4;
|
|
||||||
/** Test value for directory type */
|
|
||||||
uint8_t const FAT_FILE_TYPE_MIN_DIR = FAT_FILE_TYPE_ROOT16;
|
|
||||||
|
|
||||||
/** date field for FAT directory entry */
|
|
||||||
static inline uint16_t FAT_DATE(uint16_t year, uint8_t month, uint8_t day) {
|
|
||||||
return (year - 1980) << 9 | month << 5 | day;
|
|
||||||
}
|
|
||||||
/** year part of FAT directory date field */
|
|
||||||
static inline uint16_t FAT_YEAR(uint16_t fatDate) {
|
|
||||||
return 1980 + (fatDate >> 9);
|
|
||||||
}
|
|
||||||
/** month part of FAT directory date field */
|
|
||||||
static inline uint8_t FAT_MONTH(uint16_t fatDate) {
|
|
||||||
return (fatDate >> 5) & 0XF;
|
|
||||||
}
|
|
||||||
/** day part of FAT directory date field */
|
|
||||||
static inline uint8_t FAT_DAY(uint16_t fatDate) {
|
|
||||||
return fatDate & 0X1F;
|
|
||||||
}
|
|
||||||
/** time field for FAT directory entry */
|
|
||||||
static inline uint16_t FAT_TIME(uint8_t hour, uint8_t minute, uint8_t second) {
|
|
||||||
return hour << 11 | minute << 5 | second >> 1;
|
|
||||||
}
|
|
||||||
/** hour part of FAT directory time field */
|
|
||||||
static inline uint8_t FAT_HOUR(uint16_t fatTime) {
|
|
||||||
return fatTime >> 11;
|
|
||||||
}
|
|
||||||
/** minute part of FAT directory time field */
|
|
||||||
static inline uint8_t FAT_MINUTE(uint16_t fatTime) {
|
|
||||||
return(fatTime >> 5) & 0X3F;
|
|
||||||
}
|
|
||||||
/** second part of FAT directory time field */
|
|
||||||
static inline uint8_t FAT_SECOND(uint16_t fatTime) {
|
|
||||||
return 2*(fatTime & 0X1F);
|
|
||||||
}
|
|
||||||
/** Default date for file timestamps is 1 Jan 2000 */
|
|
||||||
uint16_t const FAT_DEFAULT_DATE = ((2000 - 1980) << 9) | (1 << 5) | 1;
|
|
||||||
/** Default time for file timestamp is 1 am */
|
|
||||||
uint16_t const FAT_DEFAULT_TIME = (1 << 11);
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* \class SdFile
|
|
||||||
* \brief Access FAT16 and FAT32 files on SD and SDHC cards.
|
|
||||||
*/
|
|
||||||
class SdFile : public Print {
|
|
||||||
public:
|
|
||||||
/** Create an instance of SdFile. */
|
|
||||||
SdFile(void) : type_(FAT_FILE_TYPE_CLOSED) {}
|
|
||||||
/**
|
|
||||||
* writeError is set to true if an error occurs during a write().
|
|
||||||
* Set writeError to false before calling print() and/or write() and check
|
|
||||||
* for true after calls to print() and/or write().
|
|
||||||
*/
|
|
||||||
//bool writeError;
|
|
||||||
/**
|
|
||||||
* Cancel unbuffered reads for this file.
|
|
||||||
* See setUnbufferedRead()
|
|
||||||
*/
|
|
||||||
void clearUnbufferedRead(void) {
|
|
||||||
flags_ &= ~F_FILE_UNBUFFERED_READ;
|
|
||||||
}
|
|
||||||
uint8_t close(void);
|
|
||||||
uint8_t contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock);
|
|
||||||
uint8_t createContiguous(SdFile* dirFile,
|
|
||||||
const char* fileName, uint32_t size);
|
|
||||||
/** \return The current cluster number for a file or directory. */
|
|
||||||
uint32_t curCluster(void) const {return curCluster_;}
|
|
||||||
/** \return The current position for a file or directory. */
|
|
||||||
uint32_t curPosition(void) const {return curPosition_;}
|
|
||||||
/**
|
|
||||||
* Set the date/time callback function
|
|
||||||
*
|
|
||||||
* \param[in] dateTime The user's call back function. The callback
|
|
||||||
* function is of the form:
|
|
||||||
*
|
|
||||||
* \code
|
|
||||||
* void dateTime(uint16_t* date, uint16_t* time) {
|
|
||||||
* uint16_t year;
|
|
||||||
* uint8_t month, day, hour, minute, second;
|
|
||||||
*
|
|
||||||
* // User gets date and time from GPS or real-time clock here
|
|
||||||
*
|
|
||||||
* // return date using FAT_DATE macro to format fields
|
|
||||||
* *date = FAT_DATE(year, month, day);
|
|
||||||
*
|
|
||||||
* // return time using FAT_TIME macro to format fields
|
|
||||||
* *time = FAT_TIME(hour, minute, second);
|
|
||||||
* }
|
|
||||||
* \endcode
|
|
||||||
*
|
|
||||||
* Sets the function that is called when a file is created or when
|
|
||||||
* a file's directory entry is modified by sync(). All timestamps,
|
|
||||||
* access, creation, and modify, are set when a file is created.
|
|
||||||
* sync() maintains the last access date and last modify date/time.
|
|
||||||
*
|
|
||||||
* See the timestamp() function.
|
|
||||||
*/
|
|
||||||
static void dateTimeCallback(
|
|
||||||
void (*dateTime)(uint16_t* date, uint16_t* time)) {
|
|
||||||
dateTime_ = dateTime;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Cancel the date/time callback function.
|
|
||||||
*/
|
|
||||||
static void dateTimeCallbackCancel(void) {
|
|
||||||
// use explicit zero since NULL is not defined for Sanguino
|
|
||||||
dateTime_ = 0;
|
|
||||||
}
|
|
||||||
/** \return Address of the block that contains this file's directory. */
|
|
||||||
uint32_t dirBlock(void) const {return dirBlock_;}
|
|
||||||
uint8_t dirEntry(dir_t* dir);
|
|
||||||
/** \return Index of this file's directory in the block dirBlock. */
|
|
||||||
uint8_t dirIndex(void) const {return dirIndex_;}
|
|
||||||
static void dirName(const dir_t& dir, char* name);
|
|
||||||
/** \return The total number of bytes in a file or directory. */
|
|
||||||
uint32_t fileSize(void) const {return fileSize_;}
|
|
||||||
/** \return The first cluster number for a file or directory. */
|
|
||||||
uint32_t firstCluster(void) const {return firstCluster_;}
|
|
||||||
/** \return True if this is a SdFile for a directory else false. */
|
|
||||||
uint8_t isDir(void) const {return type_ >= FAT_FILE_TYPE_MIN_DIR;}
|
|
||||||
/** \return True if this is a SdFile for a file else false. */
|
|
||||||
uint8_t isFile(void) const {return type_ == FAT_FILE_TYPE_NORMAL;}
|
|
||||||
/** \return True if this is a SdFile for an open file/directory else false. */
|
|
||||||
uint8_t isOpen(void) const {return type_ != FAT_FILE_TYPE_CLOSED;}
|
|
||||||
/** \return True if this is a SdFile for a subdirectory else false. */
|
|
||||||
uint8_t isSubDir(void) const {return type_ == FAT_FILE_TYPE_SUBDIR;}
|
|
||||||
/** \return True if this is a SdFile for the root directory. */
|
|
||||||
uint8_t isRoot(void) const {
|
|
||||||
return type_ == FAT_FILE_TYPE_ROOT16 || type_ == FAT_FILE_TYPE_ROOT32;
|
|
||||||
}
|
|
||||||
void ls(uint8_t flags = 0, uint8_t indent = 0);
|
|
||||||
uint8_t makeDir(SdFile* dir, const char* dirName);
|
|
||||||
uint8_t open(SdFile* dirFile, uint16_t index, uint8_t oflag);
|
|
||||||
uint8_t open(SdFile* dirFile, const char* fileName, uint8_t oflag);
|
|
||||||
|
|
||||||
uint8_t openRoot(SdVolume* vol);
|
|
||||||
static void printDirName(const dir_t& dir, uint8_t width);
|
|
||||||
static void printFatDate(uint16_t fatDate);
|
|
||||||
static void printFatTime(uint16_t fatTime);
|
|
||||||
static void printTwoDigits(uint8_t v);
|
|
||||||
/**
|
|
||||||
* 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 read(void) {
|
|
||||||
uint8_t b;
|
|
||||||
return read(&b, 1) == 1 ? b : -1;
|
|
||||||
}
|
|
||||||
int16_t read(void* buf, uint16_t nbyte);
|
|
||||||
int8_t readDir(dir_t* dir);
|
|
||||||
static uint8_t remove(SdFile* dirFile, const char* fileName);
|
|
||||||
uint8_t remove(void);
|
|
||||||
/** Set the file's current position to zero. */
|
|
||||||
void rewind(void) {
|
|
||||||
curPosition_ = curCluster_ = 0;
|
|
||||||
}
|
|
||||||
uint8_t rmDir(void);
|
|
||||||
uint8_t rmRfStar(void);
|
|
||||||
/** Set the files position to current position + \a pos. See seekSet(). */
|
|
||||||
uint8_t seekCur(uint32_t pos) {
|
|
||||||
return seekSet(curPosition_ + pos);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Set the files current position to end of file. Useful to position
|
|
||||||
* a file for append. See seekSet().
|
|
||||||
*/
|
|
||||||
uint8_t seekEnd(void) {return seekSet(fileSize_);}
|
|
||||||
uint8_t seekSet(uint32_t pos);
|
|
||||||
/**
|
|
||||||
* Use unbuffered reads to access this file. Used with Wave
|
|
||||||
* Shield ISR. Used with Sd2Card::partialBlockRead() in WaveRP.
|
|
||||||
*
|
|
||||||
* Not recommended for normal applications.
|
|
||||||
*/
|
|
||||||
void setUnbufferedRead(void) {
|
|
||||||
if (isFile()) flags_ |= F_FILE_UNBUFFERED_READ;
|
|
||||||
}
|
|
||||||
uint8_t timestamp(uint8_t flag, uint16_t year, uint8_t month, uint8_t day,
|
|
||||||
uint8_t hour, uint8_t minute, uint8_t second);
|
|
||||||
uint8_t sync(void);
|
|
||||||
/** Type of this SdFile. You should use isFile() or isDir() instead of type()
|
|
||||||
* if possible.
|
|
||||||
*
|
|
||||||
* \return The file or directory type.
|
|
||||||
*/
|
|
||||||
uint8_t type(void) const {return type_;}
|
|
||||||
uint8_t truncate(uint32_t size);
|
|
||||||
/** \return Unbuffered read flag. */
|
|
||||||
uint8_t unbufferedRead(void) const {
|
|
||||||
return flags_ & F_FILE_UNBUFFERED_READ;
|
|
||||||
}
|
|
||||||
/** \return SdVolume that contains this file. */
|
|
||||||
SdVolume* volume(void) const {return vol_;}
|
|
||||||
size_t write(uint8_t b);
|
|
||||||
size_t write(const void* buf, uint16_t nbyte);
|
|
||||||
size_t write(const char* str);
|
|
||||||
#ifdef __AVR__
|
|
||||||
void write_P(PGM_P str);
|
|
||||||
void writeln_P(PGM_P str);
|
|
||||||
#endif
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
#if ALLOW_DEPRECATED_FUNCTIONS
|
|
||||||
// Deprecated functions - suppress cpplint warnings with NOLINT comment
|
|
||||||
/** \deprecated Use:
|
|
||||||
* uint8_t SdFile::contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock);
|
|
||||||
*/
|
|
||||||
uint8_t contiguousRange(uint32_t& bgnBlock, uint32_t& endBlock) { // NOLINT
|
|
||||||
return contiguousRange(&bgnBlock, &endBlock);
|
|
||||||
}
|
|
||||||
/** \deprecated Use:
|
|
||||||
* uint8_t SdFile::createContiguous(SdFile* dirFile,
|
|
||||||
* const char* fileName, uint32_t size)
|
|
||||||
*/
|
|
||||||
uint8_t createContiguous(SdFile& dirFile, // NOLINT
|
|
||||||
const char* fileName, uint32_t size) {
|
|
||||||
return createContiguous(&dirFile, fileName, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \deprecated Use:
|
|
||||||
* static void SdFile::dateTimeCallback(
|
|
||||||
* void (*dateTime)(uint16_t* date, uint16_t* time));
|
|
||||||
*/
|
|
||||||
static void dateTimeCallback(
|
|
||||||
void (*dateTime)(uint16_t& date, uint16_t& time)) { // NOLINT
|
|
||||||
oldDateTime_ = dateTime;
|
|
||||||
dateTime_ = dateTime ? oldToNew : 0;
|
|
||||||
}
|
|
||||||
/** \deprecated Use: uint8_t SdFile::dirEntry(dir_t* dir); */
|
|
||||||
uint8_t dirEntry(dir_t& dir) {return dirEntry(&dir);} // NOLINT
|
|
||||||
/** \deprecated Use:
|
|
||||||
* uint8_t SdFile::makeDir(SdFile* dir, const char* dirName);
|
|
||||||
*/
|
|
||||||
uint8_t makeDir(SdFile& dir, const char* dirName) { // NOLINT
|
|
||||||
return makeDir(&dir, dirName);
|
|
||||||
}
|
|
||||||
/** \deprecated Use:
|
|
||||||
* uint8_t SdFile::open(SdFile* dirFile, const char* fileName, uint8_t oflag);
|
|
||||||
*/
|
|
||||||
uint8_t open(SdFile& dirFile, // NOLINT
|
|
||||||
const char* fileName, uint8_t oflag) {
|
|
||||||
return open(&dirFile, fileName, oflag);
|
|
||||||
}
|
|
||||||
/** \deprecated Do not use in new apps */
|
|
||||||
uint8_t open(SdFile& dirFile, const char* fileName) { // NOLINT
|
|
||||||
return open(dirFile, fileName, O_RDWR);
|
|
||||||
}
|
|
||||||
/** \deprecated Use:
|
|
||||||
* uint8_t SdFile::open(SdFile* dirFile, uint16_t index, uint8_t oflag);
|
|
||||||
*/
|
|
||||||
uint8_t open(SdFile& dirFile, uint16_t index, uint8_t oflag) { // NOLINT
|
|
||||||
return open(&dirFile, index, oflag);
|
|
||||||
}
|
|
||||||
/** \deprecated Use: uint8_t SdFile::openRoot(SdVolume* vol); */
|
|
||||||
uint8_t openRoot(SdVolume& vol) {return openRoot(&vol);} // NOLINT
|
|
||||||
|
|
||||||
/** \deprecated Use: int8_t SdFile::readDir(dir_t* dir); */
|
|
||||||
int8_t readDir(dir_t& dir) {return readDir(&dir);} // NOLINT
|
|
||||||
/** \deprecated Use:
|
|
||||||
* static uint8_t SdFile::remove(SdFile* dirFile, const char* fileName);
|
|
||||||
*/
|
|
||||||
static uint8_t remove(SdFile& dirFile, const char* fileName) { // NOLINT
|
|
||||||
return remove(&dirFile, fileName);
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// rest are private
|
|
||||||
private:
|
|
||||||
static void (*oldDateTime_)(uint16_t& date, uint16_t& time); // NOLINT
|
|
||||||
static void oldToNew(uint16_t* date, uint16_t* time) {
|
|
||||||
uint16_t d;
|
|
||||||
uint16_t t;
|
|
||||||
oldDateTime_(d, t);
|
|
||||||
*date = d;
|
|
||||||
*time = t;
|
|
||||||
}
|
|
||||||
#endif // ALLOW_DEPRECATED_FUNCTIONS
|
|
||||||
private:
|
|
||||||
// bits defined in flags_
|
|
||||||
// should be 0XF
|
|
||||||
static uint8_t const F_OFLAG = (O_ACCMODE | O_APPEND | O_SYNC);
|
|
||||||
// available bits
|
|
||||||
static uint8_t const F_UNUSED = 0X30;
|
|
||||||
// use unbuffered SD read
|
|
||||||
static uint8_t const F_FILE_UNBUFFERED_READ = 0X40;
|
|
||||||
// sync of directory entry required
|
|
||||||
static uint8_t const F_FILE_DIR_DIRTY = 0X80;
|
|
||||||
|
|
||||||
// make sure F_OFLAG is ok
|
|
||||||
#if ((F_UNUSED | F_FILE_UNBUFFERED_READ | F_FILE_DIR_DIRTY) & F_OFLAG)
|
|
||||||
#error flags_ bits conflict
|
|
||||||
#endif // flags_ bits
|
|
||||||
|
|
||||||
// private data
|
|
||||||
uint8_t flags_; // See above for definition of flags_ bits
|
|
||||||
uint8_t type_; // type of file see above for values
|
|
||||||
uint32_t curCluster_; // cluster for current file position
|
|
||||||
uint32_t curPosition_; // current file position in bytes from beginning
|
|
||||||
uint32_t dirBlock_; // SD block that contains directory entry for file
|
|
||||||
uint8_t dirIndex_; // index of entry in dirBlock 0 <= dirIndex_ <= 0XF
|
|
||||||
uint32_t fileSize_; // file size in bytes
|
|
||||||
uint32_t firstCluster_; // first cluster of file
|
|
||||||
SdVolume* vol_; // volume where file is located
|
|
||||||
|
|
||||||
// private functions
|
|
||||||
uint8_t addCluster(void);
|
|
||||||
uint8_t addDirCluster(void);
|
|
||||||
dir_t* cacheDirEntry(uint8_t action);
|
|
||||||
static void (*dateTime_)(uint16_t* date, uint16_t* time);
|
|
||||||
static uint8_t make83Name(const char* str, uint8_t* name);
|
|
||||||
uint8_t openCachedEntry(uint8_t cacheIndex, uint8_t oflags);
|
|
||||||
dir_t* readDirCache(void);
|
|
||||||
};
|
|
||||||
//==============================================================================
|
|
||||||
// SdVolume class
|
|
||||||
/**
|
|
||||||
* \brief Cache for an SD data block
|
|
||||||
*/
|
|
||||||
union cache_t {
|
|
||||||
/** Used to access cached file data blocks. */
|
|
||||||
uint8_t data[512];
|
|
||||||
/** Used to access cached FAT16 entries. */
|
|
||||||
uint16_t fat16[256];
|
|
||||||
/** Used to access cached FAT32 entries. */
|
|
||||||
uint32_t fat32[128];
|
|
||||||
/** Used to access cached directory entries. */
|
|
||||||
dir_t dir[16];
|
|
||||||
/** Used to access a cached MasterBoot Record. */
|
|
||||||
mbr_t mbr;
|
|
||||||
/** Used to access to a cached FAT boot sector. */
|
|
||||||
fbs_t fbs;
|
|
||||||
};
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* \class SdVolume
|
|
||||||
* \brief Access FAT16 and FAT32 volumes on SD and SDHC cards.
|
|
||||||
*/
|
|
||||||
class SdVolume {
|
|
||||||
public:
|
|
||||||
/** Create an instance of SdVolume */
|
|
||||||
SdVolume(void) :allocSearchStart_(2), fatType_(0) {}
|
|
||||||
/** Clear the cache and returns a pointer to the cache. Used by the WaveRP
|
|
||||||
* recorder to do raw write to the SD card. Not for normal apps.
|
|
||||||
*/
|
|
||||||
static uint8_t* cacheClear(void) {
|
|
||||||
cacheFlush();
|
|
||||||
cacheBlockNumber_ = 0XFFFFFFFF;
|
|
||||||
return cacheBuffer_.data;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Initialize a FAT volume. Try partition one first then try super
|
|
||||||
* floppy format.
|
|
||||||
*
|
|
||||||
* \param[in] dev The Sd2Card where the volume is located.
|
|
||||||
*
|
|
||||||
* \return The value one, true, is returned for success and
|
|
||||||
* the value zero, false, is returned for failure. Reasons for
|
|
||||||
* failure include not finding a valid partition, not finding a valid
|
|
||||||
* FAT file system or an I/O error.
|
|
||||||
*/
|
|
||||||
uint8_t init(Sd2Card* dev) { return init(dev, 1) ? true : init(dev, 0);}
|
|
||||||
uint8_t init(Sd2Card* dev, uint8_t part);
|
|
||||||
|
|
||||||
// inline functions that return volume info
|
|
||||||
/** \return The volume's cluster size in blocks. */
|
|
||||||
uint8_t blocksPerCluster(void) const {return blocksPerCluster_;}
|
|
||||||
/** \return The number of blocks in one FAT. */
|
|
||||||
uint32_t blocksPerFat(void) const {return blocksPerFat_;}
|
|
||||||
/** \return The total number of clusters in the volume. */
|
|
||||||
uint32_t clusterCount(void) const {return clusterCount_;}
|
|
||||||
/** \return The shift count required to multiply by blocksPerCluster. */
|
|
||||||
uint8_t clusterSizeShift(void) const {return clusterSizeShift_;}
|
|
||||||
/** \return The logical block number for the start of file data. */
|
|
||||||
uint32_t dataStartBlock(void) const {return dataStartBlock_;}
|
|
||||||
/** \return The number of FAT structures on the volume. */
|
|
||||||
uint8_t fatCount(void) const {return fatCount_;}
|
|
||||||
/** \return The logical block number for the start of the first FAT. */
|
|
||||||
uint32_t fatStartBlock(void) const {return fatStartBlock_;}
|
|
||||||
/** \return The FAT type of the volume. Values are 12, 16 or 32. */
|
|
||||||
uint8_t fatType(void) const {return fatType_;}
|
|
||||||
/** \return The number of entries in the root directory for FAT16 volumes. */
|
|
||||||
uint32_t rootDirEntryCount(void) const {return rootDirEntryCount_;}
|
|
||||||
/** \return The logical block number for the start of the root directory
|
|
||||||
on FAT16 volumes or the first cluster number on FAT32 volumes. */
|
|
||||||
uint32_t rootDirStart(void) const {return rootDirStart_;}
|
|
||||||
/** return a pointer to the Sd2Card object for this volume */
|
|
||||||
static Sd2Card* sdCard(void) {return sdCard_;}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
#if ALLOW_DEPRECATED_FUNCTIONS
|
|
||||||
// Deprecated functions - suppress cpplint warnings with NOLINT comment
|
|
||||||
/** \deprecated Use: uint8_t SdVolume::init(Sd2Card* dev); */
|
|
||||||
uint8_t init(Sd2Card& dev) {return init(&dev);} // NOLINT
|
|
||||||
|
|
||||||
/** \deprecated Use: uint8_t SdVolume::init(Sd2Card* dev, uint8_t vol); */
|
|
||||||
uint8_t init(Sd2Card& dev, uint8_t part) { // NOLINT
|
|
||||||
return init(&dev, part);
|
|
||||||
}
|
|
||||||
#endif // ALLOW_DEPRECATED_FUNCTIONS
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
private:
|
|
||||||
// Allow SdFile access to SdVolume private data.
|
|
||||||
friend class SdFile;
|
|
||||||
|
|
||||||
// value for action argument in cacheRawBlock to indicate read from cache
|
|
||||||
static uint8_t const CACHE_FOR_READ = 0;
|
|
||||||
// value for action argument in cacheRawBlock to indicate cache dirty
|
|
||||||
static uint8_t const CACHE_FOR_WRITE = 1;
|
|
||||||
|
|
||||||
static cache_t cacheBuffer_; // 512 byte cache for device blocks
|
|
||||||
static uint32_t cacheBlockNumber_; // Logical number of block in the cache
|
|
||||||
static Sd2Card* sdCard_; // Sd2Card object for cache
|
|
||||||
static uint8_t cacheDirty_; // cacheFlush() will write block if true
|
|
||||||
static uint32_t cacheMirrorBlock_; // block number for mirror FAT
|
|
||||||
//
|
|
||||||
uint32_t allocSearchStart_; // start cluster for alloc search
|
|
||||||
uint8_t blocksPerCluster_; // cluster size in blocks
|
|
||||||
uint32_t blocksPerFat_; // FAT size in blocks
|
|
||||||
uint32_t clusterCount_; // clusters in one FAT
|
|
||||||
uint8_t clusterSizeShift_; // shift to convert cluster count to block count
|
|
||||||
uint32_t dataStartBlock_; // first data block number
|
|
||||||
uint8_t fatCount_; // number of FATs on volume
|
|
||||||
uint32_t fatStartBlock_; // start block for first FAT
|
|
||||||
uint8_t fatType_; // volume type (12, 16, OR 32)
|
|
||||||
uint16_t rootDirEntryCount_; // number of entries in FAT16 root dir
|
|
||||||
uint32_t rootDirStart_; // root start block for FAT16, cluster for FAT32
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
uint8_t allocContiguous(uint32_t count, uint32_t* curCluster);
|
|
||||||
uint8_t blockOfCluster(uint32_t position) const {
|
|
||||||
return (position >> 9) & (blocksPerCluster_ - 1);}
|
|
||||||
uint32_t clusterStartBlock(uint32_t cluster) const {
|
|
||||||
return dataStartBlock_ + ((cluster - 2) << clusterSizeShift_);}
|
|
||||||
uint32_t blockNumber(uint32_t cluster, uint32_t position) const {
|
|
||||||
return clusterStartBlock(cluster) + blockOfCluster(position);}
|
|
||||||
static uint8_t cacheFlush(void);
|
|
||||||
static uint8_t cacheRawBlock(uint32_t blockNumber, uint8_t action);
|
|
||||||
static void cacheSetDirty(void) {cacheDirty_ |= CACHE_FOR_WRITE;}
|
|
||||||
static uint8_t cacheZeroBlock(uint32_t blockNumber);
|
|
||||||
uint8_t chainSize(uint32_t beginCluster, uint32_t* size) const;
|
|
||||||
uint8_t fatGet(uint32_t cluster, uint32_t* value) const;
|
|
||||||
uint8_t fatPut(uint32_t cluster, uint32_t value);
|
|
||||||
uint8_t fatPutEOC(uint32_t cluster) {
|
|
||||||
return fatPut(cluster, 0x0FFFFFFF);
|
|
||||||
}
|
|
||||||
uint8_t freeChain(uint32_t cluster);
|
|
||||||
uint8_t isEOC(uint32_t cluster) const {
|
|
||||||
return cluster >= (fatType_ == 16 ? FAT16EOC_MIN : FAT32EOC_MIN);
|
|
||||||
}
|
|
||||||
uint8_t readBlock(uint32_t block, uint8_t* dst) {
|
|
||||||
return sdCard_->readBlock(block, dst);}
|
|
||||||
uint8_t readData(uint32_t block, uint16_t offset,
|
|
||||||
uint16_t count, uint8_t* dst) {
|
|
||||||
return sdCard_->readData(block, offset, count, dst);
|
|
||||||
}
|
|
||||||
uint8_t writeBlock(uint32_t block, const uint8_t* dst) {
|
|
||||||
return sdCard_->writeBlock(block, dst);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#endif // SdFat_h
|
|
@ -1,75 +0,0 @@
|
|||||||
/* Arduino SdFat Library
|
|
||||||
* Copyright (C) 2008 by William Greiman
|
|
||||||
*
|
|
||||||
* This file is part of the Arduino SdFat Library
|
|
||||||
*
|
|
||||||
* This Library is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This Library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with the Arduino SdFat Library. If not, see
|
|
||||||
* <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#ifndef SdFatUtil_h
|
|
||||||
#define SdFatUtil_h
|
|
||||||
/**
|
|
||||||
* \file
|
|
||||||
* Useful utility functions.
|
|
||||||
*/
|
|
||||||
#include <Arduino.h>
|
|
||||||
#ifdef __AVR__
|
|
||||||
#include <avr/pgmspace.h>
|
|
||||||
/** Store and print a string in flash memory.*/
|
|
||||||
#define PgmPrint(x) SerialPrint_P(PSTR(x))
|
|
||||||
/** Store and print a string in flash memory followed by a CR/LF.*/
|
|
||||||
#define PgmPrintln(x) SerialPrintln_P(PSTR(x))
|
|
||||||
/** Defined so doxygen works for function definitions. */
|
|
||||||
#endif
|
|
||||||
#define NOINLINE __attribute__((noinline,unused))
|
|
||||||
#define UNUSEDOK __attribute__((unused))
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/** Return the number of bytes currently free in RAM. */
|
|
||||||
static UNUSEDOK int FreeRam(void) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
#ifdef __AVR__
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* %Print a string in flash memory to the serial port.
|
|
||||||
*
|
|
||||||
* \param[in] str Pointer to string stored in flash memory.
|
|
||||||
*/
|
|
||||||
static NOINLINE void SerialPrint_P(PGM_P str) {
|
|
||||||
for (uint8_t c; (c = pgm_read_byte(str)); str++) Serial.write(c);
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* %Print a string in flash memory followed by a CR/LF.
|
|
||||||
*
|
|
||||||
* \param[in] str Pointer to string stored in flash memory.
|
|
||||||
*/
|
|
||||||
static NOINLINE void SerialPrintln_P(PGM_P str) {
|
|
||||||
SerialPrint_P(str);
|
|
||||||
Serial.println();
|
|
||||||
}
|
|
||||||
#endif // __AVR__
|
|
||||||
#endif // #define SdFatUtil_h
|
|
@ -1,202 +0,0 @@
|
|||||||
/* Arduino SdFat Library
|
|
||||||
* Copyright (C) 2009 by William Greiman
|
|
||||||
*
|
|
||||||
* This file is part of the Arduino SdFat Library
|
|
||||||
*
|
|
||||||
* This Library is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This Library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with the Arduino SdFat Library. If not, see
|
|
||||||
* <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
\mainpage Arduino SdFat Library
|
|
||||||
<CENTER>Copyright © 2009 by William Greiman
|
|
||||||
</CENTER>
|
|
||||||
|
|
||||||
\section Intro Introduction
|
|
||||||
The Arduino SdFat Library is a minimal implementation of FAT16 and FAT32
|
|
||||||
file systems on SD flash memory cards. Standard SD and high capacity
|
|
||||||
SDHC cards are supported.
|
|
||||||
|
|
||||||
The SdFat only supports short 8.3 names.
|
|
||||||
|
|
||||||
The main classes in SdFat are Sd2Card, SdVolume, and SdFile.
|
|
||||||
|
|
||||||
The Sd2Card class supports access to standard SD cards and SDHC cards. Most
|
|
||||||
applications will only need to call the Sd2Card::init() member function.
|
|
||||||
|
|
||||||
The SdVolume class supports FAT16 and FAT32 partitions. Most applications
|
|
||||||
will only need to call the SdVolume::init() member function.
|
|
||||||
|
|
||||||
The SdFile class provides file access functions such as open(), read(),
|
|
||||||
remove(), write(), close() and sync(). This class supports access to the root
|
|
||||||
directory and subdirectories.
|
|
||||||
|
|
||||||
A number of example are provided in the SdFat/examples folder. These were
|
|
||||||
developed to test SdFat and illustrate its use.
|
|
||||||
|
|
||||||
SdFat was developed for high speed data recording. SdFat was used to implement
|
|
||||||
an audio record/play class, WaveRP, for the Adafruit Wave Shield. This
|
|
||||||
application uses special Sd2Card calls to write to contiguous files in raw mode.
|
|
||||||
These functions reduce write latency so that audio can be recorded with the
|
|
||||||
small amount of RAM in the Arduino.
|
|
||||||
|
|
||||||
\section SDcard SD\SDHC Cards
|
|
||||||
|
|
||||||
Arduinos access SD cards using the cards SPI protocol. PCs, Macs, and
|
|
||||||
most consumer devices use the 4-bit parallel SD protocol. A card that
|
|
||||||
functions well on A PC or Mac may not work well on the Arduino.
|
|
||||||
|
|
||||||
Most cards have good SPI read performance but cards vary widely in SPI
|
|
||||||
write performance. Write performance is limited by how efficiently the
|
|
||||||
card manages internal erase/remapping operations. The Arduino cannot
|
|
||||||
optimize writes to reduce erase operations because of its limit RAM.
|
|
||||||
|
|
||||||
SanDisk cards generally have good write performance. They seem to have
|
|
||||||
more internal RAM buffering than other cards and therefore can limit
|
|
||||||
the number of flash erase operations that the Arduino forces due to its
|
|
||||||
limited RAM.
|
|
||||||
|
|
||||||
\section Hardware Hardware Configuration
|
|
||||||
|
|
||||||
SdFat was developed using an
|
|
||||||
<A HREF = "http://www.adafruit.com/"> Adafruit Industries</A>
|
|
||||||
<A HREF = "http://www.ladyada.net/make/waveshield/"> Wave Shield</A>.
|
|
||||||
|
|
||||||
The hardware interface to the SD card should not use a resistor based level
|
|
||||||
shifter. SdFat sets the SPI bus frequency to 8 MHz which results in signal
|
|
||||||
rise times that are too slow for the edge detectors in many newer SD card
|
|
||||||
controllers when resistor voltage dividers are used.
|
|
||||||
|
|
||||||
The 5 to 3.3 V level shifter for 5 V Arduinos should be IC based like the
|
|
||||||
74HC4050N based circuit shown in the file SdLevel.png. The Adafruit Wave Shield
|
|
||||||
uses a 74AHC125N. Gravitech sells SD and MicroSD Card Adapters based on the
|
|
||||||
74LCX245.
|
|
||||||
|
|
||||||
If you are using a resistor based level shifter and are having problems try
|
|
||||||
setting the SPI bus frequency to 4 MHz. This can be done by using
|
|
||||||
card.init(SPI_HALF_SPEED) to initialize the SD card.
|
|
||||||
|
|
||||||
\section comment Bugs and Comments
|
|
||||||
|
|
||||||
If you wish to report bugs or have comments, send email to fat16lib@sbcglobal.net.
|
|
||||||
|
|
||||||
\section SdFatClass SdFat Usage
|
|
||||||
|
|
||||||
SdFat uses a slightly restricted form of short names.
|
|
||||||
Only printable ASCII characters are supported. No characters with code point
|
|
||||||
values greater than 127 are allowed. Space is not allowed even though space
|
|
||||||
was allowed in the API of early versions of DOS.
|
|
||||||
|
|
||||||
Short names are limited to 8 characters followed by an optional period (.)
|
|
||||||
and extension of up to 3 characters. The characters may be any combination
|
|
||||||
of letters and digits. The following special characters are also allowed:
|
|
||||||
|
|
||||||
$ % ' - _ @ ~ ` ! ( ) { } ^ # &
|
|
||||||
|
|
||||||
Short names are always converted to upper case and their original case
|
|
||||||
value is lost.
|
|
||||||
|
|
||||||
\note
|
|
||||||
The Arduino Print class uses character
|
|
||||||
at a time writes so it was necessary to use a \link SdFile::sync() sync() \endlink
|
|
||||||
function to control when data is written to the SD card.
|
|
||||||
|
|
||||||
\par
|
|
||||||
An application which writes to a file using \link Print::print() print()\endlink,
|
|
||||||
\link Print::println() println() \endlink
|
|
||||||
or \link SdFile::write write() \endlink must call \link SdFile::sync() sync() \endlink
|
|
||||||
at the appropriate time to force data and directory information to be written
|
|
||||||
to the SD Card. Data and directory information are also written to the SD card
|
|
||||||
when \link SdFile::close() close() \endlink is called.
|
|
||||||
|
|
||||||
\par
|
|
||||||
Applications must use care calling \link SdFile::sync() sync() \endlink
|
|
||||||
since 2048 bytes of I/O is required to update file and
|
|
||||||
directory information. This includes writing the current data block, reading
|
|
||||||
the block that contains the directory entry for update, writing the directory
|
|
||||||
block back and reading back the current data block.
|
|
||||||
|
|
||||||
It is possible to open a file with two or more instances of SdFile. A file may
|
|
||||||
be corrupted if data is written to the file by more than one instance of SdFile.
|
|
||||||
|
|
||||||
\section HowTo How to format SD Cards as FAT Volumes
|
|
||||||
|
|
||||||
You should use a freshly formatted SD card for best performance. FAT
|
|
||||||
file systems become slower if many files have been created and deleted.
|
|
||||||
This is because the directory entry for a deleted file is marked as deleted,
|
|
||||||
but is not deleted. When a new file is created, these entries must be scanned
|
|
||||||
before creating the file, a flaw in the FAT design. Also files can become
|
|
||||||
fragmented which causes reads and writes to be slower.
|
|
||||||
|
|
||||||
Microsoft operating systems support removable media formatted with a
|
|
||||||
Master Boot Record, MBR, or formatted as a super floppy with a FAT Boot Sector
|
|
||||||
in block zero.
|
|
||||||
|
|
||||||
Microsoft operating systems expect MBR formatted removable media
|
|
||||||
to have only one partition. The first partition should be used.
|
|
||||||
|
|
||||||
Microsoft operating systems do not support partitioning SD flash cards.
|
|
||||||
If you erase an SD card with a program like KillDisk, Most versions of
|
|
||||||
Windows will format the card as a super floppy.
|
|
||||||
|
|
||||||
The best way to restore an SD card's format is to use SDFormatter
|
|
||||||
which can be downloaded from:
|
|
||||||
|
|
||||||
http://www.sdcard.org/consumers/formatter/
|
|
||||||
|
|
||||||
SDFormatter aligns flash erase boundaries with file
|
|
||||||
system structures which reduces write latency and file system overhead.
|
|
||||||
|
|
||||||
SDFormatter does not have an option for FAT type so it may format
|
|
||||||
small cards as FAT12.
|
|
||||||
|
|
||||||
After the MBR is restored by SDFormatter you may need to reformat small
|
|
||||||
cards that have been formatted FAT12 to force the volume type to be FAT16.
|
|
||||||
|
|
||||||
If you reformat the SD card with an OS utility, choose a cluster size that
|
|
||||||
will result in:
|
|
||||||
|
|
||||||
4084 < CountOfClusters && CountOfClusters < 65525
|
|
||||||
|
|
||||||
The volume will then be FAT16.
|
|
||||||
|
|
||||||
If you are formatting an SD card on OS X or Linux, be sure to use the first
|
|
||||||
partition. Format this partition with a cluster count in above range.
|
|
||||||
|
|
||||||
\section References References
|
|
||||||
|
|
||||||
Adafruit Industries:
|
|
||||||
|
|
||||||
http://www.adafruit.com/
|
|
||||||
|
|
||||||
http://www.ladyada.net/make/waveshield/
|
|
||||||
|
|
||||||
The Arduino site:
|
|
||||||
|
|
||||||
http://www.arduino.cc/
|
|
||||||
|
|
||||||
For more information about FAT file systems see:
|
|
||||||
|
|
||||||
http://www.microsoft.com/whdc/system/platform/firmware/fatgen.mspx
|
|
||||||
|
|
||||||
For information about using SD cards as SPI devices see:
|
|
||||||
|
|
||||||
http://www.sdcard.org/developers/tech/sdcard/pls/Simplified_Physical_Layer_Spec.pdf
|
|
||||||
|
|
||||||
The ATmega328 datasheet:
|
|
||||||
|
|
||||||
http://www.atmel.com/dyn/resources/prod_documents/doc8161.pdf
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
File diff suppressed because it is too large
Load Diff
@ -1,232 +0,0 @@
|
|||||||
/* Arduino Sd2Card Library
|
|
||||||
* Copyright (C) 2009 by William Greiman
|
|
||||||
*
|
|
||||||
* This file is part of the Arduino Sd2Card 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 Sd2Card Library. If not, see
|
|
||||||
* <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#ifndef SdInfo_h
|
|
||||||
#define SdInfo_h
|
|
||||||
#include <stdint.h>
|
|
||||||
// Based on the document:
|
|
||||||
//
|
|
||||||
// SD Specifications
|
|
||||||
// Part 1
|
|
||||||
// Physical Layer
|
|
||||||
// Simplified Specification
|
|
||||||
// Version 2.00
|
|
||||||
// September 25, 2006
|
|
||||||
//
|
|
||||||
// www.sdcard.org/developers/tech/sdcard/pls/Simplified_Physical_Layer_Spec.pdf
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// SD card commands
|
|
||||||
/** GO_IDLE_STATE - init card in spi mode if CS low */
|
|
||||||
uint8_t const CMD0 = 0X00;
|
|
||||||
/** SEND_IF_COND - verify SD Memory Card interface operating condition.*/
|
|
||||||
uint8_t const CMD8 = 0X08;
|
|
||||||
/** SEND_CSD - read the Card Specific Data (CSD register) */
|
|
||||||
uint8_t const CMD9 = 0X09;
|
|
||||||
/** SEND_CID - read the card identification information (CID register) */
|
|
||||||
uint8_t const CMD10 = 0X0A;
|
|
||||||
/** SEND_STATUS - read the card status register */
|
|
||||||
uint8_t const CMD13 = 0X0D;
|
|
||||||
/** READ_BLOCK - read a single data block from the card */
|
|
||||||
uint8_t const CMD17 = 0X11;
|
|
||||||
/** WRITE_BLOCK - write a single data block to the card */
|
|
||||||
uint8_t const CMD24 = 0X18;
|
|
||||||
/** WRITE_MULTIPLE_BLOCK - write blocks of data until a STOP_TRANSMISSION */
|
|
||||||
uint8_t const CMD25 = 0X19;
|
|
||||||
/** ERASE_WR_BLK_START - sets the address of the first block to be erased */
|
|
||||||
uint8_t const CMD32 = 0X20;
|
|
||||||
/** ERASE_WR_BLK_END - sets the address of the last block of the continuous
|
|
||||||
range to be erased*/
|
|
||||||
uint8_t const CMD33 = 0X21;
|
|
||||||
/** ERASE - erase all previously selected blocks */
|
|
||||||
uint8_t const CMD38 = 0X26;
|
|
||||||
/** APP_CMD - escape for application specific command */
|
|
||||||
uint8_t const CMD55 = 0X37;
|
|
||||||
/** READ_OCR - read the OCR register of a card */
|
|
||||||
uint8_t const CMD58 = 0X3A;
|
|
||||||
/** SET_WR_BLK_ERASE_COUNT - Set the number of write blocks to be
|
|
||||||
pre-erased before writing */
|
|
||||||
uint8_t const ACMD23 = 0X17;
|
|
||||||
/** SD_SEND_OP_COMD - Sends host capacity support information and
|
|
||||||
activates the card's initialization process */
|
|
||||||
uint8_t const ACMD41 = 0X29;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/** status for card in the ready state */
|
|
||||||
uint8_t const R1_READY_STATE = 0X00;
|
|
||||||
/** status for card in the idle state */
|
|
||||||
uint8_t const R1_IDLE_STATE = 0X01;
|
|
||||||
/** status bit for illegal command */
|
|
||||||
uint8_t const R1_ILLEGAL_COMMAND = 0X04;
|
|
||||||
/** start data token for read or write single block*/
|
|
||||||
uint8_t const DATA_START_BLOCK = 0XFE;
|
|
||||||
/** stop token for write multiple blocks*/
|
|
||||||
uint8_t const STOP_TRAN_TOKEN = 0XFD;
|
|
||||||
/** start data token for write multiple blocks*/
|
|
||||||
uint8_t const WRITE_MULTIPLE_TOKEN = 0XFC;
|
|
||||||
/** mask for data response tokens after a write block operation */
|
|
||||||
uint8_t const DATA_RES_MASK = 0X1F;
|
|
||||||
/** write data accepted token */
|
|
||||||
uint8_t const DATA_RES_ACCEPTED = 0X05;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
typedef struct CID {
|
|
||||||
// byte 0
|
|
||||||
uint8_t mid; // Manufacturer ID
|
|
||||||
// byte 1-2
|
|
||||||
char oid[2]; // OEM/Application ID
|
|
||||||
// byte 3-7
|
|
||||||
char pnm[5]; // Product name
|
|
||||||
// byte 8
|
|
||||||
unsigned prv_m : 4; // Product revision n.m
|
|
||||||
unsigned prv_n : 4;
|
|
||||||
// byte 9-12
|
|
||||||
uint32_t psn; // Product serial number
|
|
||||||
// byte 13
|
|
||||||
unsigned mdt_year_high : 4; // Manufacturing date
|
|
||||||
unsigned reserved : 4;
|
|
||||||
// byte 14
|
|
||||||
unsigned mdt_month : 4;
|
|
||||||
unsigned mdt_year_low :4;
|
|
||||||
// byte 15
|
|
||||||
unsigned always1 : 1;
|
|
||||||
unsigned crc : 7;
|
|
||||||
}cid_t;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// CSD for version 1.00 cards
|
|
||||||
typedef struct CSDV1 {
|
|
||||||
// byte 0
|
|
||||||
unsigned reserved1 : 6;
|
|
||||||
unsigned csd_ver : 2;
|
|
||||||
// byte 1
|
|
||||||
uint8_t taac;
|
|
||||||
// byte 2
|
|
||||||
uint8_t nsac;
|
|
||||||
// byte 3
|
|
||||||
uint8_t tran_speed;
|
|
||||||
// byte 4
|
|
||||||
uint8_t ccc_high;
|
|
||||||
// byte 5
|
|
||||||
unsigned read_bl_len : 4;
|
|
||||||
unsigned ccc_low : 4;
|
|
||||||
// byte 6
|
|
||||||
unsigned c_size_high : 2;
|
|
||||||
unsigned reserved2 : 2;
|
|
||||||
unsigned dsr_imp : 1;
|
|
||||||
unsigned read_blk_misalign :1;
|
|
||||||
unsigned write_blk_misalign : 1;
|
|
||||||
unsigned read_bl_partial : 1;
|
|
||||||
// byte 7
|
|
||||||
uint8_t c_size_mid;
|
|
||||||
// byte 8
|
|
||||||
unsigned vdd_r_curr_max : 3;
|
|
||||||
unsigned vdd_r_curr_min : 3;
|
|
||||||
unsigned c_size_low :2;
|
|
||||||
// byte 9
|
|
||||||
unsigned c_size_mult_high : 2;
|
|
||||||
unsigned vdd_w_cur_max : 3;
|
|
||||||
unsigned vdd_w_curr_min : 3;
|
|
||||||
// byte 10
|
|
||||||
unsigned sector_size_high : 6;
|
|
||||||
unsigned erase_blk_en : 1;
|
|
||||||
unsigned c_size_mult_low : 1;
|
|
||||||
// byte 11
|
|
||||||
unsigned wp_grp_size : 7;
|
|
||||||
unsigned sector_size_low : 1;
|
|
||||||
// byte 12
|
|
||||||
unsigned write_bl_len_high : 2;
|
|
||||||
unsigned r2w_factor : 3;
|
|
||||||
unsigned reserved3 : 2;
|
|
||||||
unsigned wp_grp_enable : 1;
|
|
||||||
// byte 13
|
|
||||||
unsigned reserved4 : 5;
|
|
||||||
unsigned write_partial : 1;
|
|
||||||
unsigned write_bl_len_low : 2;
|
|
||||||
// byte 14
|
|
||||||
unsigned reserved5: 2;
|
|
||||||
unsigned file_format : 2;
|
|
||||||
unsigned tmp_write_protect : 1;
|
|
||||||
unsigned perm_write_protect : 1;
|
|
||||||
unsigned copy : 1;
|
|
||||||
unsigned file_format_grp : 1;
|
|
||||||
// byte 15
|
|
||||||
unsigned always1 : 1;
|
|
||||||
unsigned crc : 7;
|
|
||||||
}csd1_t;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// CSD for version 2.00 cards
|
|
||||||
typedef struct CSDV2 {
|
|
||||||
// byte 0
|
|
||||||
unsigned reserved1 : 6;
|
|
||||||
unsigned csd_ver : 2;
|
|
||||||
// byte 1
|
|
||||||
uint8_t taac;
|
|
||||||
// byte 2
|
|
||||||
uint8_t nsac;
|
|
||||||
// byte 3
|
|
||||||
uint8_t tran_speed;
|
|
||||||
// byte 4
|
|
||||||
uint8_t ccc_high;
|
|
||||||
// byte 5
|
|
||||||
unsigned read_bl_len : 4;
|
|
||||||
unsigned ccc_low : 4;
|
|
||||||
// byte 6
|
|
||||||
unsigned reserved2 : 4;
|
|
||||||
unsigned dsr_imp : 1;
|
|
||||||
unsigned read_blk_misalign :1;
|
|
||||||
unsigned write_blk_misalign : 1;
|
|
||||||
unsigned read_bl_partial : 1;
|
|
||||||
// byte 7
|
|
||||||
unsigned reserved3 : 2;
|
|
||||||
unsigned c_size_high : 6;
|
|
||||||
// byte 8
|
|
||||||
uint8_t c_size_mid;
|
|
||||||
// byte 9
|
|
||||||
uint8_t c_size_low;
|
|
||||||
// byte 10
|
|
||||||
unsigned sector_size_high : 6;
|
|
||||||
unsigned erase_blk_en : 1;
|
|
||||||
unsigned reserved4 : 1;
|
|
||||||
// byte 11
|
|
||||||
unsigned wp_grp_size : 7;
|
|
||||||
unsigned sector_size_low : 1;
|
|
||||||
// byte 12
|
|
||||||
unsigned write_bl_len_high : 2;
|
|
||||||
unsigned r2w_factor : 3;
|
|
||||||
unsigned reserved5 : 2;
|
|
||||||
unsigned wp_grp_enable : 1;
|
|
||||||
// byte 13
|
|
||||||
unsigned reserved6 : 5;
|
|
||||||
unsigned write_partial : 1;
|
|
||||||
unsigned write_bl_len_low : 2;
|
|
||||||
// byte 14
|
|
||||||
unsigned reserved7: 2;
|
|
||||||
unsigned file_format : 2;
|
|
||||||
unsigned tmp_write_protect : 1;
|
|
||||||
unsigned perm_write_protect : 1;
|
|
||||||
unsigned copy : 1;
|
|
||||||
unsigned file_format_grp : 1;
|
|
||||||
// byte 15
|
|
||||||
unsigned always1 : 1;
|
|
||||||
unsigned crc : 7;
|
|
||||||
}csd2_t;
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// union of old and new style CSD register
|
|
||||||
union csd_t {
|
|
||||||
csd1_t v1;
|
|
||||||
csd2_t v2;
|
|
||||||
};
|
|
||||||
#endif // SdInfo_h
|
|
@ -1,295 +0,0 @@
|
|||||||
/* Arduino SdFat Library
|
|
||||||
* Copyright (C) 2009 by William Greiman
|
|
||||||
*
|
|
||||||
* This file is part of the Arduino SdFat Library
|
|
||||||
*
|
|
||||||
* This Library is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This Library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with the Arduino SdFat Library. If not, see
|
|
||||||
* <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#include "SdFat.h"
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// raw block cache
|
|
||||||
// init cacheBlockNumber_to invalid SD block number
|
|
||||||
uint32_t SdVolume::cacheBlockNumber_ = 0XFFFFFFFF;
|
|
||||||
cache_t SdVolume::cacheBuffer_; // 512 byte cache for Sd2Card
|
|
||||||
Sd2Card* SdVolume::sdCard_; // pointer to SD card object
|
|
||||||
uint8_t SdVolume::cacheDirty_ = 0; // cacheFlush() will write block if true
|
|
||||||
uint32_t SdVolume::cacheMirrorBlock_ = 0; // mirror block for second FAT
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// find a contiguous group of clusters
|
|
||||||
uint8_t SdVolume::allocContiguous(uint32_t count, uint32_t* curCluster) {
|
|
||||||
// start of group
|
|
||||||
uint32_t bgnCluster;
|
|
||||||
|
|
||||||
// flag to save place to start next search
|
|
||||||
uint8_t 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 = 1 == count;
|
|
||||||
}
|
|
||||||
// end of group
|
|
||||||
uint32_t endCluster = bgnCluster;
|
|
||||||
|
|
||||||
// last cluster of FAT
|
|
||||||
uint32_t fatEnd = clusterCount_ + 1;
|
|
||||||
|
|
||||||
// search the FAT for free clusters
|
|
||||||
for (uint32_t n = 0;; n++, endCluster++) {
|
|
||||||
// can't find space checked all clusters
|
|
||||||
if (n >= clusterCount_) return false;
|
|
||||||
|
|
||||||
// past end - start from beginning of FAT
|
|
||||||
if (endCluster > fatEnd) {
|
|
||||||
bgnCluster = endCluster = 2;
|
|
||||||
}
|
|
||||||
uint32_t f;
|
|
||||||
if (!fatGet(endCluster, &f)) return false;
|
|
||||||
|
|
||||||
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)) return false;
|
|
||||||
|
|
||||||
// link clusters
|
|
||||||
while (endCluster > bgnCluster) {
|
|
||||||
if (!fatPut(endCluster - 1, endCluster)) return false;
|
|
||||||
endCluster--;
|
|
||||||
}
|
|
||||||
if (*curCluster != 0) {
|
|
||||||
// connect chains
|
|
||||||
if (!fatPut(*curCluster, bgnCluster)) return false;
|
|
||||||
}
|
|
||||||
// return first cluster number to caller
|
|
||||||
*curCluster = bgnCluster;
|
|
||||||
|
|
||||||
// remember possible next free cluster
|
|
||||||
if (setStart) allocSearchStart_ = bgnCluster + 1;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
uint8_t SdVolume::cacheFlush(void) {
|
|
||||||
if (cacheDirty_) {
|
|
||||||
if (!sdCard_->writeBlock(cacheBlockNumber_, cacheBuffer_.data)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// mirror FAT tables
|
|
||||||
if (cacheMirrorBlock_) {
|
|
||||||
if (!sdCard_->writeBlock(cacheMirrorBlock_, cacheBuffer_.data)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
cacheMirrorBlock_ = 0;
|
|
||||||
}
|
|
||||||
cacheDirty_ = 0;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
uint8_t SdVolume::cacheRawBlock(uint32_t blockNumber, uint8_t action) {
|
|
||||||
if (cacheBlockNumber_ != blockNumber) {
|
|
||||||
if (!cacheFlush()) return false;
|
|
||||||
if (!sdCard_->readBlock(blockNumber, cacheBuffer_.data)) return false;
|
|
||||||
cacheBlockNumber_ = blockNumber;
|
|
||||||
}
|
|
||||||
cacheDirty_ |= action;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// cache a zero block for blockNumber
|
|
||||||
uint8_t SdVolume::cacheZeroBlock(uint32_t blockNumber) {
|
|
||||||
if (!cacheFlush()) return false;
|
|
||||||
|
|
||||||
// loop take less flash than memset(cacheBuffer_.data, 0, 512);
|
|
||||||
for (uint16_t i = 0; i < 512; i++) {
|
|
||||||
cacheBuffer_.data[i] = 0;
|
|
||||||
}
|
|
||||||
cacheBlockNumber_ = blockNumber;
|
|
||||||
cacheSetDirty();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// return the size in bytes of a cluster chain
|
|
||||||
uint8_t SdVolume::chainSize(uint32_t cluster, uint32_t* size) const {
|
|
||||||
uint32_t s = 0;
|
|
||||||
do {
|
|
||||||
if (!fatGet(cluster, &cluster)) return false;
|
|
||||||
s += 512UL << clusterSizeShift_;
|
|
||||||
} while (!isEOC(cluster));
|
|
||||||
*size = s;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Fetch a FAT entry
|
|
||||||
uint8_t SdVolume::fatGet(uint32_t cluster, uint32_t* value) const {
|
|
||||||
if (cluster > (clusterCount_ + 1)) return false;
|
|
||||||
uint32_t lba = fatStartBlock_;
|
|
||||||
lba += fatType_ == 16 ? cluster >> 8 : cluster >> 7;
|
|
||||||
if (lba != cacheBlockNumber_) {
|
|
||||||
if (!cacheRawBlock(lba, CACHE_FOR_READ)) return false;
|
|
||||||
}
|
|
||||||
if (fatType_ == 16) {
|
|
||||||
*value = cacheBuffer_.fat16[cluster & 0XFF];
|
|
||||||
} else {
|
|
||||||
*value = cacheBuffer_.fat32[cluster & 0X7F] & FAT32MASK;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Store a FAT entry
|
|
||||||
uint8_t SdVolume::fatPut(uint32_t cluster, uint32_t value) {
|
|
||||||
// error if reserved cluster
|
|
||||||
if (cluster < 2) return false;
|
|
||||||
|
|
||||||
// error if not in FAT
|
|
||||||
if (cluster > (clusterCount_ + 1)) return false;
|
|
||||||
|
|
||||||
// calculate block address for entry
|
|
||||||
uint32_t lba = fatStartBlock_;
|
|
||||||
lba += fatType_ == 16 ? cluster >> 8 : cluster >> 7;
|
|
||||||
|
|
||||||
if (lba != cacheBlockNumber_) {
|
|
||||||
if (!cacheRawBlock(lba, CACHE_FOR_READ)) return false;
|
|
||||||
}
|
|
||||||
// store entry
|
|
||||||
if (fatType_ == 16) {
|
|
||||||
cacheBuffer_.fat16[cluster & 0XFF] = value;
|
|
||||||
} else {
|
|
||||||
cacheBuffer_.fat32[cluster & 0X7F] = value;
|
|
||||||
}
|
|
||||||
cacheSetDirty();
|
|
||||||
|
|
||||||
// mirror second FAT
|
|
||||||
if (fatCount_ > 1) cacheMirrorBlock_ = lba + blocksPerFat_;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// free a cluster chain
|
|
||||||
uint8_t SdVolume::freeChain(uint32_t cluster) {
|
|
||||||
// clear free cluster location
|
|
||||||
allocSearchStart_ = 2;
|
|
||||||
|
|
||||||
do {
|
|
||||||
uint32_t next;
|
|
||||||
if (!fatGet(cluster, &next)) return false;
|
|
||||||
|
|
||||||
// free cluster
|
|
||||||
if (!fatPut(cluster, 0)) return false;
|
|
||||||
|
|
||||||
cluster = next;
|
|
||||||
} while (!isEOC(cluster));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
uint8_t SdVolume::init(Sd2Card* dev, uint8_t part) {
|
|
||||||
uint32_t volumeStartBlock = 0;
|
|
||||||
sdCard_ = dev;
|
|
||||||
// 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)return false;
|
|
||||||
if (!cacheRawBlock(volumeStartBlock, CACHE_FOR_READ)) return false;
|
|
||||||
part_t* p = &cacheBuffer_.mbr.part[part-1];
|
|
||||||
if ((p->boot & 0X7F) !=0 ||
|
|
||||||
p->totalSectors < 100 ||
|
|
||||||
p->firstSector == 0) {
|
|
||||||
// not a valid partition
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
volumeStartBlock = p->firstSector;
|
|
||||||
}
|
|
||||||
if (!cacheRawBlock(volumeStartBlock, CACHE_FOR_READ)) return false;
|
|
||||||
bpb_t* bpb = &cacheBuffer_.fbs.bpb;
|
|
||||||
if (bpb->bytesPerSector != 512 ||
|
|
||||||
bpb->fatCount == 0 ||
|
|
||||||
bpb->reservedSectorCount == 0 ||
|
|
||||||
bpb->sectorsPerCluster == 0) {
|
|
||||||
// not valid FAT volume
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
fatCount_ = bpb->fatCount;
|
|
||||||
blocksPerCluster_ = bpb->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) return false;
|
|
||||||
}
|
|
||||||
blocksPerFat_ = bpb->sectorsPerFat16 ?
|
|
||||||
bpb->sectorsPerFat16 : bpb->sectorsPerFat32;
|
|
||||||
|
|
||||||
fatStartBlock_ = volumeStartBlock + bpb->reservedSectorCount;
|
|
||||||
|
|
||||||
// count for FAT16 zero for FAT32
|
|
||||||
rootDirEntryCount_ = bpb->rootDirEntryCount;
|
|
||||||
|
|
||||||
// directory start for FAT16 dataStart for FAT32
|
|
||||||
rootDirStart_ = fatStartBlock_ + bpb->fatCount * blocksPerFat_;
|
|
||||||
|
|
||||||
// data start for FAT16 and FAT32
|
|
||||||
dataStartBlock_ = rootDirStart_ + ((32 * bpb->rootDirEntryCount + 511)/512);
|
|
||||||
|
|
||||||
// total blocks for FAT16 or FAT32
|
|
||||||
uint32_t totalBlocks = bpb->totalSectors16 ?
|
|
||||||
bpb->totalSectors16 : bpb->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;
|
|
||||||
} else if (clusterCount_ < 65525) {
|
|
||||||
fatType_ = 16;
|
|
||||||
} else {
|
|
||||||
rootDirStart_ = bpb->fat32RootCluster;
|
|
||||||
fatType_ = 32;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
10
libraries/SDFS/library.properties
Normal file
10
libraries/SDFS/library.properties
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name=SDFS
|
||||||
|
version=0.1.0
|
||||||
|
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||||
|
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||||
|
sentence=ESP8266 FS filesystem for use on SD cards using Bill Greiman's amazing FAT16/FAT32 Arduino library.
|
||||||
|
paragraph=ESP8266 FS filesystem for use on SD cards using Bill Greiman's amazing FAT16/FAT32 Arduino library.
|
||||||
|
category=Data Storage
|
||||||
|
url=https://github.com/esp8266/Arduino
|
||||||
|
architectures=esp8266
|
||||||
|
dot_a_linkage=true
|
154
libraries/SDFS/src/SDFS.cpp
Normal file
154
libraries/SDFS/src/SDFS.cpp
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
SDFS.cpp - file system wrapper for SdFat
|
||||||
|
Copyright (c) 2019 Earle F. Philhower, III. All rights reserved.
|
||||||
|
|
||||||
|
Based on spiffs_api.cpp which is:
|
||||||
|
| Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
|
||||||
|
|
||||||
|
This code was influenced by NodeMCU and Sming libraries, and first version of
|
||||||
|
Arduino wrapper written by Hristo Gochkov.
|
||||||
|
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#include "SDFS.h"
|
||||||
|
#include "SDFSFormatter.h"
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
|
using namespace fs;
|
||||||
|
|
||||||
|
|
||||||
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SDFS)
|
||||||
|
FS SDFS = FS(FSImplPtr(new sdfs::SDFSImpl()));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace sdfs {
|
||||||
|
|
||||||
|
|
||||||
|
FileImplPtr SDFSImpl::open(const char* path, OpenMode openMode, AccessMode accessMode)
|
||||||
|
{
|
||||||
|
if (!_mounted) {
|
||||||
|
DEBUGV("SDFSImpl::open() called on unmounted FS\n");
|
||||||
|
return FileImplPtr();
|
||||||
|
}
|
||||||
|
if (!path || !path[0]) {
|
||||||
|
DEBUGV("SDFSImpl::open() called with invalid filename\n");
|
||||||
|
return FileImplPtr();
|
||||||
|
}
|
||||||
|
int flags = _getFlags(openMode, accessMode);
|
||||||
|
if ((openMode && OM_CREATE) && strchr(path, '/')) {
|
||||||
|
// For file creation, silently make subdirs as needed. If any fail,
|
||||||
|
// it will be caught by the real file open later on
|
||||||
|
char *pathStr = strdup(path);
|
||||||
|
if (pathStr) {
|
||||||
|
// Make dirs up to the final fnamepart
|
||||||
|
char *ptr = strrchr(pathStr, '/');
|
||||||
|
if (ptr && ptr != pathStr) { // Don't try to make root dir!
|
||||||
|
*ptr = 0;
|
||||||
|
_fs.mkdir(pathStr, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(pathStr);
|
||||||
|
}
|
||||||
|
sdfat::File fd = _fs.open(path, flags);
|
||||||
|
if (!fd) {
|
||||||
|
DEBUGV("SDFSImpl::openFile: fd=%p path=`%s` openMode=%d accessMode=%d",
|
||||||
|
&fd, path, openMode, accessMode);
|
||||||
|
return FileImplPtr();
|
||||||
|
}
|
||||||
|
auto sharedFd = std::make_shared<sdfat::File>(fd);
|
||||||
|
return std::make_shared<SDFSFileImpl>(this, sharedFd, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
DirImplPtr SDFSImpl::openDir(const char* path)
|
||||||
|
{
|
||||||
|
if (!_mounted) {
|
||||||
|
return DirImplPtr();
|
||||||
|
}
|
||||||
|
char *pathStr = strdup(path); // Allow edits on our scratch copy
|
||||||
|
if (!pathStr) {
|
||||||
|
// OOM
|
||||||
|
return DirImplPtr();
|
||||||
|
}
|
||||||
|
// Get rid of any trailing slashes
|
||||||
|
while (strlen(pathStr) && (pathStr[strlen(pathStr)-1]=='/')) {
|
||||||
|
pathStr[strlen(pathStr)-1] = 0;
|
||||||
|
}
|
||||||
|
// At this point we have a name of "/blah/blah/blah" or "blah" or ""
|
||||||
|
// If that references a directory, just open it and we're done.
|
||||||
|
sdfat::File dirFile;
|
||||||
|
const char *filter = "";
|
||||||
|
if (!pathStr[0]) {
|
||||||
|
// openDir("") === openDir("/")
|
||||||
|
dirFile = _fs.open("/", sdfat::O_RDONLY);
|
||||||
|
filter = "";
|
||||||
|
} else if (_fs.exists(pathStr)) {
|
||||||
|
dirFile = _fs.open(pathStr, sdfat::O_RDONLY);
|
||||||
|
if (dirFile.isDir()) {
|
||||||
|
// Easy peasy, path specifies an existing dir!
|
||||||
|
filter = "";
|
||||||
|
} else {
|
||||||
|
dirFile.close();
|
||||||
|
// This is a file, so open the containing dir
|
||||||
|
char *ptr = strrchr(pathStr, '/');
|
||||||
|
if (!ptr) {
|
||||||
|
// No slashes, open the root dir
|
||||||
|
dirFile = _fs.open("/", sdfat::O_RDONLY);
|
||||||
|
filter = pathStr;
|
||||||
|
} else {
|
||||||
|
// We've got slashes, open the dir one up
|
||||||
|
*ptr = 0; // Remove slash, truncare string
|
||||||
|
dirFile = _fs.open(pathStr, sdfat::O_RDONLY);
|
||||||
|
filter = ptr + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Name doesn't exist, so use the parent dir of whatever was sent in
|
||||||
|
// This is a file, so open the containing dir
|
||||||
|
char *ptr = strrchr(pathStr, '/');
|
||||||
|
if (!ptr) {
|
||||||
|
// No slashes, open the root dir
|
||||||
|
dirFile = _fs.open("/", sdfat::O_RDONLY);
|
||||||
|
filter = pathStr;
|
||||||
|
} else {
|
||||||
|
// We've got slashes, open the dir one up
|
||||||
|
*ptr = 0; // Remove slash, truncare string
|
||||||
|
dirFile = _fs.open(pathStr, sdfat::O_RDONLY);
|
||||||
|
filter = ptr + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!dirFile) {
|
||||||
|
DEBUGV("SDFSImpl::openDir: path=`%s`\n", path);
|
||||||
|
return DirImplPtr();
|
||||||
|
}
|
||||||
|
auto sharedDir = std::make_shared<sdfat::File>(dirFile);
|
||||||
|
auto ret = std::make_shared<SDFSDirImpl>(filter, this, sharedDir, pathStr);
|
||||||
|
free(pathStr);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDFSImpl::format() {
|
||||||
|
if (_mounted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SDFSFormatter formatter;
|
||||||
|
bool ret = formatter.format(&_fs, _cfg._csPin, _cfg._spiSettings);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}; // namespace sdfs
|
||||||
|
|
419
libraries/SDFS/src/SDFS.h
Normal file
419
libraries/SDFS/src/SDFS.h
Normal file
@ -0,0 +1,419 @@
|
|||||||
|
#ifndef SDFS_H
|
||||||
|
#define SDFS_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
SDFS.h - file system wrapper for SdLib
|
||||||
|
Copyright (c) 2019 Earle F. Philhower, III. All rights reserved.
|
||||||
|
|
||||||
|
Based on spiffs_api.h, which is:
|
||||||
|
| Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
|
||||||
|
|
||||||
|
This code was influenced by NodeMCU and Sming libraries, and first version of
|
||||||
|
Arduino wrapper written by Hristo Gochkov.
|
||||||
|
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#include <limits>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "FS.h"
|
||||||
|
#include "FSImpl.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <SdFat.h>
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
|
using namespace fs;
|
||||||
|
|
||||||
|
namespace sdfs {
|
||||||
|
|
||||||
|
class SDFSFileImpl;
|
||||||
|
class SDFSDirImpl;
|
||||||
|
class SDFSConfig : public FSConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SDFSConfig() {
|
||||||
|
_type = SDFSConfig::fsid::FSId;
|
||||||
|
_autoFormat = false;
|
||||||
|
_csPin = 4;
|
||||||
|
_spiSettings = SD_SCK_MHZ(10);
|
||||||
|
_part = 0;
|
||||||
|
}
|
||||||
|
SDFSConfig(uint8_t csPin, SPISettings spi) {
|
||||||
|
_type = SDFSConfig::fsid::FSId;
|
||||||
|
_autoFormat = false;
|
||||||
|
_csPin = csPin;
|
||||||
|
_spiSettings = spi;
|
||||||
|
_part = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum fsid { FSId = 0x53444653 };
|
||||||
|
|
||||||
|
SDFSConfig setAutoFormat(bool val = true) {
|
||||||
|
_autoFormat = val;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
SDFSConfig setCSPin(uint8_t pin) {
|
||||||
|
_csPin = pin;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
SDFSConfig setSPI(SPISettings spi) {
|
||||||
|
_spiSettings = spi;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
SDFSConfig setPart(uint8_t part) {
|
||||||
|
_part = part;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inherit _type and _autoFormat
|
||||||
|
uint8_t _csPin;
|
||||||
|
uint8_t _part;
|
||||||
|
SPISettings _spiSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SDFSImpl : public FSImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SDFSImpl() : _mounted(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) override;
|
||||||
|
|
||||||
|
bool exists(const char* path) {
|
||||||
|
return _mounted ? _fs.exists(path) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirImplPtr openDir(const char* path) override;
|
||||||
|
|
||||||
|
bool rename(const char* pathFrom, const char* pathTo) override {
|
||||||
|
return _mounted ? _fs.rename(pathFrom, pathTo) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool info(FSInfo& info) override {
|
||||||
|
if (!_mounted) {
|
||||||
|
DEBUGV("SDFS::info: FS not mounted\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
info.maxOpenFiles = 999; // TODO - not valid
|
||||||
|
info.blockSize = _fs.vol()->blocksPerCluster() * 512;
|
||||||
|
info.pageSize = 0; // TODO ?
|
||||||
|
info.maxPathLength = 255; // TODO ?
|
||||||
|
info.totalBytes =_fs.vol()->volumeBlockCount() * 512;
|
||||||
|
info.usedBytes = info.totalBytes - (_fs.vol()->freeClusterCount() * _fs.vol()->blocksPerCluster() * 512);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool remove(const char* path) override {
|
||||||
|
return _mounted ? _fs.remove(path) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mkdir(const char* path) override {
|
||||||
|
return _mounted ? _fs.mkdir(path) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rmdir(const char* path) override {
|
||||||
|
return _mounted ?_fs.rmdir(path) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setConfig(const FSConfig &cfg) override
|
||||||
|
{
|
||||||
|
if ((cfg._type != SDFSConfig::fsid::FSId) || _mounted) {
|
||||||
|
DEBUGV("SDFS::setConfig: invalid config or already mounted\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_cfg = *static_cast<const SDFSConfig *>(&cfg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool begin() override {
|
||||||
|
if (_mounted) {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
_mounted = _fs.begin(_cfg._csPin, _cfg._spiSettings);
|
||||||
|
if (!_mounted && _cfg._autoFormat) {
|
||||||
|
format();
|
||||||
|
_mounted = _fs.begin(_cfg._csPin, _cfg._spiSettings);
|
||||||
|
}
|
||||||
|
return _mounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
void end() override {
|
||||||
|
_mounted = false;
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
bool format() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class SDFileImpl;
|
||||||
|
friend class SDFSDirImpl;
|
||||||
|
|
||||||
|
sdfat::SdFat* getFs()
|
||||||
|
{
|
||||||
|
return &_fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t _getFlags(OpenMode openMode, AccessMode accessMode) {
|
||||||
|
uint8_t mode = 0;
|
||||||
|
if (openMode & OM_CREATE) {
|
||||||
|
mode |= sdfat::O_CREAT;
|
||||||
|
}
|
||||||
|
if (openMode & OM_APPEND) {
|
||||||
|
mode |= sdfat::O_AT_END;
|
||||||
|
}
|
||||||
|
if (openMode & OM_TRUNCATE) {
|
||||||
|
mode |= sdfat::O_TRUNC;
|
||||||
|
}
|
||||||
|
if (accessMode & AM_READ) {
|
||||||
|
mode |= sdfat::O_READ;
|
||||||
|
}
|
||||||
|
if (accessMode & AM_WRITE) {
|
||||||
|
mode |= sdfat::O_WRITE;
|
||||||
|
}
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
sdfat::SdFat _fs;
|
||||||
|
SDFSConfig _cfg;
|
||||||
|
bool _mounted;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class SDFSFileImpl : public FileImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SDFSFileImpl(SDFSImpl *fs, std::shared_ptr<sdfat::File> fd, const char *name)
|
||||||
|
: _fs(fs), _fd(fd), _opened(true)
|
||||||
|
{
|
||||||
|
_name = std::shared_ptr<char>(new char[strlen(name) + 1], std::default_delete<char[]>());
|
||||||
|
strcpy(_name.get(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
~SDFSFileImpl() override
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t write(const uint8_t *buf, size_t size) override
|
||||||
|
{
|
||||||
|
return _opened ? _fd->write(buf, size) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t read(uint8_t* buf, size_t size) override
|
||||||
|
{
|
||||||
|
return _opened ? _fd->read(buf, size) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush() override
|
||||||
|
{
|
||||||
|
if (_opened) {
|
||||||
|
_fd->flush();
|
||||||
|
_fd->sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool seek(uint32_t pos, SeekMode mode) override
|
||||||
|
{
|
||||||
|
if (!_opened) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
switch (mode) {
|
||||||
|
case SeekSet:
|
||||||
|
return _fd->seekSet(pos);
|
||||||
|
case SeekEnd:
|
||||||
|
return _fd->seekEnd(-pos); // TODO again, odd from POSIX
|
||||||
|
case SeekCur:
|
||||||
|
return _fd->seekCur(pos);
|
||||||
|
default:
|
||||||
|
// Should not be hit, we've got an invalid seek mode
|
||||||
|
DEBUGV("SDFSFileImpl::seek: invalid seek mode %d\n", mode);
|
||||||
|
assert((mode==SeekSet) || (mode==SeekEnd) || (mode==SeekCur)); // Will fail and give meaningful assert message
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t position() const override
|
||||||
|
{
|
||||||
|
return _opened ? _fd->curPosition() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const override
|
||||||
|
{
|
||||||
|
return _opened ? _fd->fileSize() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool truncate(uint32_t size) override
|
||||||
|
{
|
||||||
|
if (!_opened) {
|
||||||
|
DEBUGV("SDFSFileImpl::truncate: file not opened\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _fd->truncate(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() override
|
||||||
|
{
|
||||||
|
if (_opened) {
|
||||||
|
_fd->close();
|
||||||
|
_opened = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* name() const override
|
||||||
|
{
|
||||||
|
if (!_opened) {
|
||||||
|
DEBUGV("SDFSFileImpl::name: file not opened\n");
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
const char *p = _name.get();
|
||||||
|
const char *slash = strrchr(p, '/');
|
||||||
|
// For names w/o any path elements, return directly
|
||||||
|
// If there are slashes, return name after the last slash
|
||||||
|
// (note that strrchr will return the address of the slash,
|
||||||
|
// so need to increment to ckip it)
|
||||||
|
return (slash && slash[1]) ? slash + 1 : p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* fullName() const override
|
||||||
|
{
|
||||||
|
return _opened ? _name.get() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFile() const override
|
||||||
|
{
|
||||||
|
return _opened ? _fd->isFile() : false;;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDirectory() const override
|
||||||
|
{
|
||||||
|
return _opened ? _fd->isDirectory() : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
SDFSImpl* _fs;
|
||||||
|
std::shared_ptr<sdfat::File> _fd;
|
||||||
|
std::shared_ptr<char> _name;
|
||||||
|
bool _opened;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SDFSDirImpl : public DirImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SDFSDirImpl(const String& pattern, SDFSImpl* fs, std::shared_ptr<sdfat::File> dir, const char *dirPath = nullptr)
|
||||||
|
: _pattern(pattern), _fs(fs), _dir(dir), _valid(false), _dirPath(nullptr)
|
||||||
|
{
|
||||||
|
if (dirPath) {
|
||||||
|
_dirPath = std::shared_ptr<char>(new char[strlen(dirPath) + 1], std::default_delete<char[]>());
|
||||||
|
strcpy(_dirPath.get(), dirPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~SDFSDirImpl() override
|
||||||
|
{
|
||||||
|
_dir->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) override
|
||||||
|
{
|
||||||
|
if (!_valid) {
|
||||||
|
return FileImplPtr();
|
||||||
|
}
|
||||||
|
// MAX_PATH on FAT32 is potentially 260 bytes per most implementations
|
||||||
|
char tmpName[260];
|
||||||
|
snprintf(tmpName, sizeof(tmpName), "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get()&&_dirPath.get()[0]?"/":"", _lfn);
|
||||||
|
return _fs->open((const char *)tmpName, openMode, accessMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* fileName() override
|
||||||
|
{
|
||||||
|
if (!_valid) {
|
||||||
|
DEBUGV("SDFSDirImpl::fileName: directory not valid\n");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return (const char*) _lfn; //_dirent.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t fileSize() override
|
||||||
|
{
|
||||||
|
if (!_valid) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFile() const override
|
||||||
|
{
|
||||||
|
return _valid ? _isFile : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDirectory() const override
|
||||||
|
{
|
||||||
|
return _valid ? _isDirectory : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool next() override
|
||||||
|
{
|
||||||
|
const int n = _pattern.length();
|
||||||
|
do {
|
||||||
|
sdfat::File file;
|
||||||
|
file.openNext(_dir.get(), sdfat::O_READ);
|
||||||
|
if (file) {
|
||||||
|
_valid = 1;
|
||||||
|
_size = file.fileSize();
|
||||||
|
_isFile = file.isFile();
|
||||||
|
_isDirectory = file.isDirectory();
|
||||||
|
file.getName(_lfn, sizeof(_lfn));
|
||||||
|
file.close();
|
||||||
|
} else {
|
||||||
|
_valid = 0;
|
||||||
|
}
|
||||||
|
} while(_valid && strncmp((const char*) _lfn, _pattern.c_str(), n) != 0);
|
||||||
|
return _valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rewind() override
|
||||||
|
{
|
||||||
|
_valid = false;
|
||||||
|
_dir->rewind();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
String _pattern;
|
||||||
|
SDFSImpl* _fs;
|
||||||
|
std::shared_ptr<sdfat::File> _dir;
|
||||||
|
bool _valid;
|
||||||
|
char _lfn[64];
|
||||||
|
std::shared_ptr<char> _dirPath;
|
||||||
|
uint32_t _size;
|
||||||
|
bool _isFile;
|
||||||
|
bool _isDirectory;
|
||||||
|
};
|
||||||
|
|
||||||
|
}; // namespace sdfs
|
||||||
|
|
||||||
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SDFS)
|
||||||
|
extern FS SDFS;
|
||||||
|
using sdfs::SDFSConfig;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // SDFS.h
|
405
libraries/SDFS/src/SDFSFormatter.h
Normal file
405
libraries/SDFS/src/SDFSFormatter.h
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
/*
|
||||||
|
SDFSFormatter.cpp - Formatter for SdFat SD cards
|
||||||
|
Copyright (c) 2019 Earle F. Philhower, III. All rights reserved.
|
||||||
|
|
||||||
|
A C++ implementation of the SdFat/examples/SdFormatter sketch:
|
||||||
|
| Copyright (c) 2011-2018 Bill Greiman
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _SDFSFORMATTER_H
|
||||||
|
#define _SDFSFORMATTER_H
|
||||||
|
|
||||||
|
#include "SDFS.h"
|
||||||
|
#include <FS.h>
|
||||||
|
#include <PolledTimeout.h>
|
||||||
|
|
||||||
|
namespace sdfs {
|
||||||
|
|
||||||
|
class SDFSFormatter {
|
||||||
|
private:
|
||||||
|
// Taken from main FS object
|
||||||
|
sdfat::Sd2Card *card;
|
||||||
|
sdfat::cache_t *cache;
|
||||||
|
|
||||||
|
uint32_t cardSizeBlocks;
|
||||||
|
uint32_t cardCapacityMB;
|
||||||
|
|
||||||
|
|
||||||
|
// MBR information
|
||||||
|
uint8_t partType;
|
||||||
|
uint32_t relSector;
|
||||||
|
uint32_t partSize;
|
||||||
|
|
||||||
|
// Fake disk geometry
|
||||||
|
uint8_t numberOfHeads;
|
||||||
|
uint8_t sectorsPerTrack;
|
||||||
|
|
||||||
|
// FAT parameters
|
||||||
|
uint16_t reservedSectors;
|
||||||
|
uint8_t sectorsPerCluster;
|
||||||
|
uint32_t fatStart;
|
||||||
|
uint32_t fatSize;
|
||||||
|
uint32_t dataStart;
|
||||||
|
|
||||||
|
uint8_t writeCache(uint32_t lbn) {
|
||||||
|
return card->writeBlock(lbn, cache->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearCache(uint8_t addSig) {
|
||||||
|
memset(cache, 0, sizeof(*cache));
|
||||||
|
if (addSig) {
|
||||||
|
cache->mbr.mbrSig0 = sdfat::BOOTSIG0;
|
||||||
|
cache->mbr.mbrSig1 = sdfat::BOOTSIG1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool clearFatDir(uint32_t bgn, uint32_t count) {
|
||||||
|
clearCache(false);
|
||||||
|
if (!card->writeStart(bgn, count)) {
|
||||||
|
DEBUGV("SDFS: Clear FAT/DIR writeStart failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
esp8266::polledTimeout::periodic timeToYield(5); // Yield every 5ms of runtime
|
||||||
|
for (uint32_t i = 0; i < count; i++) {
|
||||||
|
if (timeToYield) {
|
||||||
|
delay(0); // WDT feed
|
||||||
|
}
|
||||||
|
if (!card->writeData(cache->data)) {
|
||||||
|
DEBUGV("SDFS: Clear FAT/DIR writeData failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!card->writeStop()) {
|
||||||
|
DEBUGV("SDFS: Clear FAT/DIR writeStop failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t lbnToCylinder(uint32_t lbn) {
|
||||||
|
return lbn / (numberOfHeads * sectorsPerTrack);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t lbnToHead(uint32_t lbn) {
|
||||||
|
return (lbn % (numberOfHeads * sectorsPerTrack)) / sectorsPerTrack;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t lbnToSector(uint32_t lbn) {
|
||||||
|
return (lbn % sectorsPerTrack) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool writeMbr() {
|
||||||
|
clearCache(true);
|
||||||
|
sdfat::part_t* p = cache->mbr.part;
|
||||||
|
p->boot = 0;
|
||||||
|
uint16_t c = lbnToCylinder(relSector);
|
||||||
|
if (c > 1023) {
|
||||||
|
DEBUGV("SDFS: MBR CHS");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
p->beginCylinderHigh = c >> 8;
|
||||||
|
p->beginCylinderLow = c & 0XFF;
|
||||||
|
p->beginHead = lbnToHead(relSector);
|
||||||
|
p->beginSector = lbnToSector(relSector);
|
||||||
|
p->type = partType;
|
||||||
|
uint32_t endLbn = relSector + partSize - 1;
|
||||||
|
c = lbnToCylinder(endLbn);
|
||||||
|
if (c <= 1023) {
|
||||||
|
p->endCylinderHigh = c >> 8;
|
||||||
|
p->endCylinderLow = c & 0XFF;
|
||||||
|
p->endHead = lbnToHead(endLbn);
|
||||||
|
p->endSector = lbnToSector(endLbn);
|
||||||
|
} else {
|
||||||
|
// Too big flag, c = 1023, h = 254, s = 63
|
||||||
|
p->endCylinderHigh = 3;
|
||||||
|
p->endCylinderLow = 255;
|
||||||
|
p->endHead = 254;
|
||||||
|
p->endSector = 63;
|
||||||
|
}
|
||||||
|
p->firstSector = relSector;
|
||||||
|
p->totalSectors = partSize;
|
||||||
|
if (!writeCache(0)) {
|
||||||
|
DEBUGV("SDFS: write MBR");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t volSerialNumber() {
|
||||||
|
return (cardSizeBlocks << 8) + micros();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool makeFat16() {
|
||||||
|
uint16_t const BU16 = 128;
|
||||||
|
uint32_t nc;
|
||||||
|
for (dataStart = 2 * BU16;; dataStart += BU16) {
|
||||||
|
nc = (cardSizeBlocks - dataStart)/sectorsPerCluster;
|
||||||
|
fatSize = (nc + 2 + 255)/256;
|
||||||
|
uint32_t r = BU16 + 1 + 2 * fatSize + 32;
|
||||||
|
if (dataStart < r) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
relSector = dataStart - r + BU16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// check valid cluster count for FAT16 volume
|
||||||
|
if (nc < 4085 || nc >= 65525) {
|
||||||
|
DEBUGV("SDFS: Bad cluster count");
|
||||||
|
}
|
||||||
|
reservedSectors = 1;
|
||||||
|
fatStart = relSector + reservedSectors;
|
||||||
|
partSize = nc * sectorsPerCluster + 2 * fatSize + reservedSectors + 32;
|
||||||
|
if (partSize < 32680) {
|
||||||
|
partType = 0X01;
|
||||||
|
} else if (partSize < 65536) {
|
||||||
|
partType = 0X04;
|
||||||
|
} else {
|
||||||
|
partType = 0X06;
|
||||||
|
}
|
||||||
|
// write MBR
|
||||||
|
if (!writeMbr()) {
|
||||||
|
DEBUGV("SDFS: writembr failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCache(true);
|
||||||
|
sdfat::fat_boot_t* pb = &cache->fbs;
|
||||||
|
pb->jump[0] = 0XEB;
|
||||||
|
pb->jump[1] = 0X00;
|
||||||
|
pb->jump[2] = 0X90;
|
||||||
|
for (uint8_t i = 0; i < sizeof(pb->oemId); i++) {
|
||||||
|
pb->oemId[i] = ' ';
|
||||||
|
}
|
||||||
|
pb->bytesPerSector = 512;
|
||||||
|
pb->sectorsPerCluster = sectorsPerCluster;
|
||||||
|
pb->reservedSectorCount = reservedSectors;
|
||||||
|
pb->fatCount = 2;
|
||||||
|
pb->rootDirEntryCount = 512;
|
||||||
|
pb->mediaType = 0XF8;
|
||||||
|
pb->sectorsPerFat16 = fatSize;
|
||||||
|
pb->sectorsPerTrack = sectorsPerTrack;
|
||||||
|
pb->headCount = numberOfHeads;
|
||||||
|
pb->hidddenSectors = relSector;
|
||||||
|
pb->totalSectors32 = partSize;
|
||||||
|
pb->driveNumber = 0X80;
|
||||||
|
pb->bootSignature = sdfat::EXTENDED_BOOT_SIG;
|
||||||
|
pb->volumeSerialNumber = volSerialNumber();
|
||||||
|
memcpy_P(pb->volumeLabel, PSTR("NO NAME "), sizeof(pb->volumeLabel));
|
||||||
|
memcpy_P(pb->fileSystemType, PSTR("FAT16 "), sizeof(pb->fileSystemType));
|
||||||
|
// write partition boot sector
|
||||||
|
if (!writeCache(relSector)) {
|
||||||
|
DEBUGV("SDFS: FAT16 write PBS failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// clear FAT and root directory
|
||||||
|
if (!clearFatDir(fatStart, dataStart - fatStart)) {
|
||||||
|
DEBUGV("SDFS: FAT16 clear root failed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
clearCache(false);
|
||||||
|
cache->fat16[0] = 0XFFF8;
|
||||||
|
cache->fat16[1] = 0XFFFF;
|
||||||
|
// write first block of FAT and backup for reserved clusters
|
||||||
|
if (!writeCache(fatStart) || !writeCache(fatStart + fatSize)) {
|
||||||
|
DEBUGV("FAT16 reserve failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool makeFat32() {
|
||||||
|
uint16_t const BU32 = 8192;
|
||||||
|
uint32_t nc;
|
||||||
|
relSector = BU32;
|
||||||
|
for (dataStart = 2 * BU32;; dataStart += BU32) {
|
||||||
|
nc = (cardSizeBlocks - dataStart)/sectorsPerCluster;
|
||||||
|
fatSize = (nc + 2 + 127)/128;
|
||||||
|
uint32_t r = relSector + 9 + 2 * fatSize;
|
||||||
|
if (dataStart >= r) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// error if too few clusters in FAT32 volume
|
||||||
|
if (nc < 65525) {
|
||||||
|
DEBUGV("SDFS: Bad cluster count");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
reservedSectors = dataStart - relSector - 2 * fatSize;
|
||||||
|
fatStart = relSector + reservedSectors;
|
||||||
|
partSize = nc * sectorsPerCluster + dataStart - relSector;
|
||||||
|
// type depends on address of end sector
|
||||||
|
// max CHS has lbn = 16450560 = 1024*255*63
|
||||||
|
if ((relSector + partSize) <= 16450560) {
|
||||||
|
// FAT32
|
||||||
|
partType = 0X0B;
|
||||||
|
} else {
|
||||||
|
// FAT32 with INT 13
|
||||||
|
partType = 0X0C;
|
||||||
|
}
|
||||||
|
if (!writeMbr()) {
|
||||||
|
DEBUGV("SDFS: writembr failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCache(true);
|
||||||
|
|
||||||
|
sdfat::fat32_boot_t* pb = &cache->fbs32;
|
||||||
|
pb->jump[0] = 0XEB;
|
||||||
|
pb->jump[1] = 0X00;
|
||||||
|
pb->jump[2] = 0X90;
|
||||||
|
for (uint8_t i = 0; i < sizeof(pb->oemId); i++) {
|
||||||
|
pb->oemId[i] = ' ';
|
||||||
|
}
|
||||||
|
pb->bytesPerSector = 512;
|
||||||
|
pb->sectorsPerCluster = sectorsPerCluster;
|
||||||
|
pb->reservedSectorCount = reservedSectors;
|
||||||
|
pb->fatCount = 2;
|
||||||
|
pb->mediaType = 0XF8;
|
||||||
|
pb->sectorsPerTrack = sectorsPerTrack;
|
||||||
|
pb->headCount = numberOfHeads;
|
||||||
|
pb->hidddenSectors = relSector;
|
||||||
|
pb->totalSectors32 = partSize;
|
||||||
|
pb->sectorsPerFat32 = fatSize;
|
||||||
|
pb->fat32RootCluster = 2;
|
||||||
|
pb->fat32FSInfo = 1;
|
||||||
|
pb->fat32BackBootBlock = 6;
|
||||||
|
pb->driveNumber = 0X80;
|
||||||
|
pb->bootSignature = sdfat::EXTENDED_BOOT_SIG;
|
||||||
|
pb->volumeSerialNumber = volSerialNumber();
|
||||||
|
memcpy_P(pb->volumeLabel, PSTR("NO NAME "), sizeof(pb->volumeLabel));
|
||||||
|
memcpy_P(pb->fileSystemType, PSTR("FAT32 "), sizeof(pb->fileSystemType));
|
||||||
|
// write partition boot sector and backup
|
||||||
|
if (!writeCache(relSector) || !writeCache(relSector + 6)) {
|
||||||
|
DEBUGV("SDFS: FAT32 write PBS failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
clearCache(true);
|
||||||
|
// write extra boot area and backup
|
||||||
|
if (!writeCache(relSector + 2) || !writeCache(relSector + 8)) {
|
||||||
|
DEBUGV("SDFS: FAT32 PBS ext failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sdfat::fat32_fsinfo_t* pf = &cache->fsinfo;
|
||||||
|
pf->leadSignature = sdfat::FSINFO_LEAD_SIG;
|
||||||
|
pf->structSignature = sdfat::FSINFO_STRUCT_SIG;
|
||||||
|
pf->freeCount = 0XFFFFFFFF;
|
||||||
|
pf->nextFree = 0XFFFFFFFF;
|
||||||
|
// write FSINFO sector and backup
|
||||||
|
if (!writeCache(relSector + 1) || !writeCache(relSector + 7)) {
|
||||||
|
DEBUGV("SDFS: FAT32 FSINFO failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
clearFatDir(fatStart, 2 * fatSize + sectorsPerCluster);
|
||||||
|
clearCache(false);
|
||||||
|
cache->fat32[0] = 0x0FFFFFF8;
|
||||||
|
cache->fat32[1] = 0x0FFFFFFF;
|
||||||
|
cache->fat32[2] = 0x0FFFFFFF;
|
||||||
|
// write first block of FAT and backup for reserved clusters
|
||||||
|
if (!writeCache(fatStart) || !writeCache(fatStart + fatSize)) {
|
||||||
|
DEBUGV("SDFS: FAT32 reserve failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool format(sdfat::SdFat *_fs, int8_t _csPin, SPISettings _spiSettings) {
|
||||||
|
card = static_cast<sdfat::Sd2Card*>(_fs->card());
|
||||||
|
cache = _fs->cacheClear();
|
||||||
|
|
||||||
|
if (!card->begin(_csPin, _spiSettings)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cardSizeBlocks = card->cardSize();
|
||||||
|
if (cardSizeBlocks == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cardCapacityMB = (cardSizeBlocks + 2047)/2048;
|
||||||
|
|
||||||
|
if (cardCapacityMB <= 6) {
|
||||||
|
return false; // Card is too small
|
||||||
|
} else if (cardCapacityMB <= 16) {
|
||||||
|
sectorsPerCluster = 2;
|
||||||
|
} else if (cardCapacityMB <= 32) {
|
||||||
|
sectorsPerCluster = 4;
|
||||||
|
} else if (cardCapacityMB <= 64) {
|
||||||
|
sectorsPerCluster = 8;
|
||||||
|
} else if (cardCapacityMB <= 128) {
|
||||||
|
sectorsPerCluster = 16;
|
||||||
|
} else if (cardCapacityMB <= 1024) {
|
||||||
|
sectorsPerCluster = 32;
|
||||||
|
} else if (cardCapacityMB <= 32768) {
|
||||||
|
sectorsPerCluster = 64;
|
||||||
|
} else {
|
||||||
|
// SDXC cards
|
||||||
|
sectorsPerCluster = 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set fake disk geometry
|
||||||
|
sectorsPerTrack = cardCapacityMB <= 256 ? 32 : 63;
|
||||||
|
|
||||||
|
if (cardCapacityMB <= 16) {
|
||||||
|
numberOfHeads = 2;
|
||||||
|
} else if (cardCapacityMB <= 32) {
|
||||||
|
numberOfHeads = 4;
|
||||||
|
} else if (cardCapacityMB <= 128) {
|
||||||
|
numberOfHeads = 8;
|
||||||
|
} else if (cardCapacityMB <= 504) {
|
||||||
|
numberOfHeads = 16;
|
||||||
|
} else if (cardCapacityMB <= 1008) {
|
||||||
|
numberOfHeads = 32;
|
||||||
|
} else if (cardCapacityMB <= 2016) {
|
||||||
|
numberOfHeads = 64;
|
||||||
|
} else if (cardCapacityMB <= 4032) {
|
||||||
|
numberOfHeads = 128;
|
||||||
|
} else {
|
||||||
|
numberOfHeads = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase all data on card (TRIM)
|
||||||
|
uint32_t const ERASE_SIZE = 262144L;
|
||||||
|
uint32_t firstBlock = 0;
|
||||||
|
uint32_t lastBlock;
|
||||||
|
do {
|
||||||
|
lastBlock = firstBlock + ERASE_SIZE - 1;
|
||||||
|
if (lastBlock >= cardSizeBlocks) {
|
||||||
|
lastBlock = cardSizeBlocks - 1;
|
||||||
|
}
|
||||||
|
if (!card->erase(firstBlock, lastBlock)) {
|
||||||
|
return false; // Erase fail
|
||||||
|
}
|
||||||
|
delay(0); // yield to the OS to avoid WDT
|
||||||
|
firstBlock += ERASE_SIZE;
|
||||||
|
} while (firstBlock < cardSizeBlocks);
|
||||||
|
|
||||||
|
if (!card->readBlock(0, cache->data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card->type() != sdfat::SD_CARD_TYPE_SDHC) {
|
||||||
|
return makeFat16();
|
||||||
|
} else {
|
||||||
|
return makeFat32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}; // class SDFSFormatter
|
||||||
|
|
||||||
|
}; // namespace sdfs
|
||||||
|
|
||||||
|
|
||||||
|
#endif // _SDFSFORMATTER_H
|
@ -87,3 +87,27 @@ coreVersionMinor LITERAL1
|
|||||||
coreVersionRevision LITERAL1
|
coreVersionRevision LITERAL1
|
||||||
coreVersionSubRevision LITERAL1
|
coreVersionSubRevision LITERAL1
|
||||||
coreVersionNumeric LITERAL1
|
coreVersionNumeric LITERAL1
|
||||||
|
|
||||||
|
# Filesystem objects
|
||||||
|
SPIFFS KEYWORD1
|
||||||
|
SDFS KEYWORD1
|
||||||
|
File KEYWORD1
|
||||||
|
Dir KEYWORD1
|
||||||
|
SPIFFSConfig KEYWORD1
|
||||||
|
|
||||||
|
# Seek enums
|
||||||
|
SeekSet LITERAL1
|
||||||
|
SeekCur LITERAL1
|
||||||
|
SeekEnd LITERAL1
|
||||||
|
|
||||||
|
# Filesystem methods (not covered by Stream or Print)
|
||||||
|
format KEYWORD2
|
||||||
|
info KEYWORD2
|
||||||
|
exists KEYWORD2
|
||||||
|
openDir KEYWORD2
|
||||||
|
remove KEYWORD2
|
||||||
|
rename KEYWORD2
|
||||||
|
mkdir KEYWORD2
|
||||||
|
rmdir KEYWORD2
|
||||||
|
isFile KEYWORD2
|
||||||
|
isDirectory KEYWORD2
|
||||||
|
@ -1,5 +1,27 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# return 1 if this test should not be built in CI (for other archs, not needed, etc.)
|
||||||
|
function skip_ino()
|
||||||
|
{
|
||||||
|
local ino=$1
|
||||||
|
local skiplist=""
|
||||||
|
# Add items to the following list with "\n" netween them to skip running. No spaces, tabs, etc. allowed
|
||||||
|
read -d '' skiplist << EOL || true
|
||||||
|
/#attic/
|
||||||
|
/AnalogBinLogger/
|
||||||
|
/LowLatencyLogger/
|
||||||
|
/LowLatencyLoggerADXL345/
|
||||||
|
/LowLatencyLoggerMPU6050/
|
||||||
|
/PrintBenchmark/
|
||||||
|
/TeensySdioDemo/
|
||||||
|
/SoftwareSpi/
|
||||||
|
/STM32Test/
|
||||||
|
/extras/
|
||||||
|
EOL
|
||||||
|
echo $ino | grep -q -F "$skiplist"
|
||||||
|
echo $(( 1 - $? ))
|
||||||
|
}
|
||||||
|
|
||||||
function print_size_info()
|
function print_size_info()
|
||||||
{
|
{
|
||||||
elf_file=$1
|
elf_file=$1
|
||||||
@ -79,6 +101,10 @@ function build_sketches()
|
|||||||
echo -e "\n ------------ Skipping $sketch ------------ \n";
|
echo -e "\n ------------ Skipping $sketch ------------ \n";
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
if [[ $(skip_ino $sketch) = 1 ]]; then
|
||||||
|
echo -e "\n ------------ Skipping $sketch ------------ \n";
|
||||||
|
continue
|
||||||
|
fi
|
||||||
echo -e "\n ------------ Building $sketch ------------ \n";
|
echo -e "\n ------------ Building $sketch ------------ \n";
|
||||||
# $arduino --verify $sketch;
|
# $arduino --verify $sketch;
|
||||||
echo "$build_cmd $sketch"
|
echo "$build_cmd $sketch"
|
||||||
|
@ -2,6 +2,7 @@ BINDIR := bin
|
|||||||
LCOV_DIRECTORY := lcov
|
LCOV_DIRECTORY := lcov
|
||||||
OUTPUT_BINARY := $(BINDIR)/host_tests
|
OUTPUT_BINARY := $(BINDIR)/host_tests
|
||||||
CORE_PATH := ../../cores/esp8266
|
CORE_PATH := ../../cores/esp8266
|
||||||
|
LIBRARIES_PATH := ../../libraries
|
||||||
FORCE32 ?= 1
|
FORCE32 ?= 1
|
||||||
OPTZ ?= -Os
|
OPTZ ?= -Os
|
||||||
V ?= 0
|
V ?= 0
|
||||||
@ -66,7 +67,18 @@ CORE_CPP_FILES := $(addprefix $(CORE_PATH)/,\
|
|||||||
libb64/cdecode.cpp \
|
libb64/cdecode.cpp \
|
||||||
Schedule.cpp \
|
Schedule.cpp \
|
||||||
HardwareSerial.cpp \
|
HardwareSerial.cpp \
|
||||||
)
|
) \
|
||||||
|
$(addprefix $(LIBRARIES_PATH)/ESP8266SdFat/src/, \
|
||||||
|
FatLib/FatFile.cpp \
|
||||||
|
FatLib/FatFileLFN.cpp \
|
||||||
|
FatLib/FatFilePrint.cpp \
|
||||||
|
FatLib/FatFileSFN.cpp \
|
||||||
|
FatLib/FatVolume.cpp \
|
||||||
|
FatLib/FmtNumber.cpp \
|
||||||
|
FatLib/StdioStream.cpp \
|
||||||
|
) \
|
||||||
|
$(LIBRARIES_PATH)/SDFS/src/SDFS.cpp \
|
||||||
|
$(LIBRARIES_PATH)/SD/src/SD.cpp
|
||||||
|
|
||||||
CORE_C_FILES := $(addprefix $(CORE_PATH)/,\
|
CORE_C_FILES := $(addprefix $(CORE_PATH)/,\
|
||||||
)
|
)
|
||||||
@ -74,6 +86,7 @@ CORE_C_FILES := $(addprefix $(CORE_PATH)/,\
|
|||||||
MOCK_CPP_FILES_COMMON := $(addprefix common/,\
|
MOCK_CPP_FILES_COMMON := $(addprefix common/,\
|
||||||
Arduino.cpp \
|
Arduino.cpp \
|
||||||
spiffs_mock.cpp \
|
spiffs_mock.cpp \
|
||||||
|
sdfs_mock.cpp \
|
||||||
WMath.cpp \
|
WMath.cpp \
|
||||||
MockUART.cpp \
|
MockUART.cpp \
|
||||||
MockTools.cpp \
|
MockTools.cpp \
|
||||||
@ -129,6 +142,7 @@ endif
|
|||||||
FLAGS += $(DEBUG) -Wall -coverage $(OPTZ) -fno-common -g $(M32)
|
FLAGS += $(DEBUG) -Wall -coverage $(OPTZ) -fno-common -g $(M32)
|
||||||
FLAGS += -DHTTPCLIENT_1_1_COMPATIBLE=0
|
FLAGS += -DHTTPCLIENT_1_1_COMPATIBLE=0
|
||||||
FLAGS += -DLWIP_IPV6=0
|
FLAGS += -DLWIP_IPV6=0
|
||||||
|
FLAGS += -DHOST_MOCK=1
|
||||||
FLAGS += -DNONOSDK221=1
|
FLAGS += -DNONOSDK221=1
|
||||||
FLAGS += $(MKFLAGS)
|
FLAGS += $(MKFLAGS)
|
||||||
CXXFLAGS += -std=c++11 $(FLAGS)
|
CXXFLAGS += -std=c++11 $(FLAGS)
|
||||||
|
21
tests/host/common/sdfs_mock.cpp
Normal file
21
tests/host/common/sdfs_mock.cpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
sdfs_mock.cpp - SDFS HAL mock for host side testing
|
||||||
|
Copyright (c) 2019 Earle F. Philhower, III
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sdfs_mock.h"
|
||||||
|
#include "../../../libraries/SDFS/src/SDFS.h"
|
||||||
|
|
||||||
|
#define SDSIZE 16LL
|
||||||
|
uint64_t _sdCardSizeB = SDSIZE * 1024LL * 1024LL;
|
||||||
|
uint8_t _sdCard[SDSIZE * 1024LL * 1024LL];
|
33
tests/host/common/sdfs_mock.h
Normal file
33
tests/host/common/sdfs_mock.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
sdfs.h - SDFS mock for host side testing
|
||||||
|
Copyright (c) 2019 Earle F. Philhower, III
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef sdfs_mock_hpp
|
||||||
|
#define sdfs_mock_hpp
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
|
class SDFSMock {
|
||||||
|
public:
|
||||||
|
SDFSMock() { }
|
||||||
|
void reset() { }
|
||||||
|
~SDFSMock() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SDFS_MOCK_DECLARE() SDFSMock sdfs_mock();
|
||||||
|
|
||||||
|
#endif /* spiffs_mock_hpp */
|
@ -17,7 +17,10 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <FS.h>
|
#include <FS.h>
|
||||||
#include "../common/spiffs_mock.h"
|
#include "../common/spiffs_mock.h"
|
||||||
|
#include "../common/sdfs_mock.h"
|
||||||
#include <spiffs/spiffs.h>
|
#include <spiffs/spiffs.h>
|
||||||
|
#include "../../../libraries/SDFS/src/SDFS.h"
|
||||||
|
#include "../../../libraries/SD/src/SD.h"
|
||||||
|
|
||||||
static void createFile (const char* name, const char* content)
|
static void createFile (const char* name, const char* content)
|
||||||
{
|
{
|
||||||
@ -48,10 +51,41 @@ static std::set<String> listDir (const char* path)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SPIFFS checks the config object passed in", "[fs]")
|
||||||
|
{
|
||||||
|
SPIFFS_MOCK_DECLARE(64, 8, 512, "");
|
||||||
|
FSConfig f;
|
||||||
|
SPIFFSConfig s;
|
||||||
|
SDFSConfig d;
|
||||||
|
|
||||||
|
REQUIRE_FALSE(SPIFFS.setConfig(f));
|
||||||
|
REQUIRE(SPIFFS.setConfig(s));
|
||||||
|
REQUIRE_FALSE(SPIFFS.setConfig(d));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SDFS checks the config object passed in", "[fs]")
|
||||||
|
{
|
||||||
|
SDFS_MOCK_DECLARE();
|
||||||
|
FSConfig f;
|
||||||
|
SPIFFSConfig s;
|
||||||
|
SDFSConfig d;
|
||||||
|
|
||||||
|
REQUIRE_FALSE(SDFS.setConfig(f));
|
||||||
|
REQUIRE_FALSE(SDFS.setConfig(s));
|
||||||
|
REQUIRE(SDFS.setConfig(d));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("FS can begin","[fs]")
|
TEST_CASE("FS can begin","[fs]")
|
||||||
{
|
{
|
||||||
SPIFFS_MOCK_DECLARE(64, 8, 512, "");
|
SPIFFS_MOCK_DECLARE(64, 8, 512, "");
|
||||||
|
SPIFFSConfig cfg;
|
||||||
|
cfg.setAutoFormat(false);
|
||||||
|
SPIFFS.setConfig(cfg);
|
||||||
|
REQUIRE_FALSE(SPIFFS.begin());
|
||||||
|
cfg.setAutoFormat(true);
|
||||||
|
SPIFFS.setConfig(cfg);
|
||||||
REQUIRE(SPIFFS.begin());
|
REQUIRE(SPIFFS.begin());
|
||||||
|
REQUIRE_FALSE(SPIFFS.setConfig(cfg)); // Can't change config of mounted FS
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("FS can't begin with zero size","[fs]")
|
TEST_CASE("FS can't begin with zero size","[fs]")
|
||||||
@ -184,3 +218,116 @@ TEST_CASE("#1819 Can list all files with openDir(\"\")", "[fs][bugreport]")
|
|||||||
auto files = listDir("");
|
auto files = listDir("");
|
||||||
REQUIRE(files.size() == 4);
|
REQUIRE(files.size() == 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("truncate", "[fs][spiffs]")
|
||||||
|
{
|
||||||
|
SPIFFS_MOCK_DECLARE(64, 8, 512, "");
|
||||||
|
REQUIRE(SPIFFS.begin());
|
||||||
|
createFile("/file1", "some text");
|
||||||
|
auto f = SPIFFS.open("/file1", "r");
|
||||||
|
f.truncate(4);
|
||||||
|
f.close();
|
||||||
|
String s = readFile("/file1");
|
||||||
|
REQUIRE( s == "some" );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SDFS", "[sdfs]")
|
||||||
|
{
|
||||||
|
SDFS_MOCK_DECLARE();
|
||||||
|
auto cfg = SDFSConfig(0, SD_SCK_MHZ(1));
|
||||||
|
SDFS.setConfig(cfg);
|
||||||
|
REQUIRE(SDFS.format());
|
||||||
|
REQUIRE(SDFS.begin());
|
||||||
|
REQUIRE_FALSE(SDFS.setConfig(cfg)); // Can't change config of mounted fs
|
||||||
|
REQUIRE(SDFS.mkdir("/happy/face"));
|
||||||
|
REQUIRE(SDFS.mkdir("/happy/nose"));
|
||||||
|
REQUIRE(SDFS.rmdir("/happy/face"));
|
||||||
|
auto f = SDFS.open("/this/is/a/long/name.txt", "w");
|
||||||
|
f.printf("Hello\n");
|
||||||
|
f.close();
|
||||||
|
SDFS.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Files.ino example", "[sd]")
|
||||||
|
{
|
||||||
|
SDFS_MOCK_DECLARE();
|
||||||
|
SDFS.end();
|
||||||
|
SDFS.setConfig(SDFSConfig(0, SD_SCK_MHZ(1)));
|
||||||
|
REQUIRE(SDFS.format());
|
||||||
|
REQUIRE(SD.begin(4));
|
||||||
|
REQUIRE_FALSE(SD.exists("example.txt"));
|
||||||
|
File myFile = SD.open("example.txt", FILE_WRITE);
|
||||||
|
REQUIRE(myFile);
|
||||||
|
myFile.close();
|
||||||
|
REQUIRE(SD.exists("example.txt"));
|
||||||
|
REQUIRE(SD.remove("example.txt"));
|
||||||
|
REQUIRE_FALSE(SD.exists("example.txt"));
|
||||||
|
SDFS.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void createFileSD(const char* name, const char* content)
|
||||||
|
{
|
||||||
|
auto f = SD.open(name, FILE_WRITE);
|
||||||
|
REQUIRE(f);
|
||||||
|
if (content) {
|
||||||
|
f.print(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String readFileSD(const char* name)
|
||||||
|
{
|
||||||
|
auto f = SD.open(name, FILE_READ);
|
||||||
|
if (f) {
|
||||||
|
return f.readString();
|
||||||
|
}
|
||||||
|
return String();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Listfiles.ino example", "[sd]")
|
||||||
|
{
|
||||||
|
SDFS_MOCK_DECLARE();
|
||||||
|
SDFS.setConfig(SDFSConfig(0, SD_SCK_MHZ(1)));
|
||||||
|
REQUIRE(SDFS.format());
|
||||||
|
REQUIRE(SD.begin(4));
|
||||||
|
|
||||||
|
createFileSD("file1", "hello");
|
||||||
|
createFileSD("file2", "hola");
|
||||||
|
createFileSD("dir1/file3", "nihao");
|
||||||
|
createFileSD("dir2/dir3/file4", "bonjour");
|
||||||
|
|
||||||
|
File root = SD.open("/");
|
||||||
|
File file1 = root.openNextFile();
|
||||||
|
File file2 = root.openNextFile();
|
||||||
|
File dir1 = root.openNextFile();
|
||||||
|
File dir1_file3 = dir1.openNextFile();
|
||||||
|
File dir2 = root.openNextFile();
|
||||||
|
File dir2_dir3 = dir2.openNextFile();
|
||||||
|
File dir2_dir3_file4 = dir2_dir3.openNextFile();
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
ok = root.isDirectory() && !root.isFile() && !strcmp(root.name(), "/");
|
||||||
|
REQUIRE(ok);
|
||||||
|
ok = !file1.isDirectory() && file1.isFile() && !strcmp(file1.name(), "file1");
|
||||||
|
REQUIRE(ok);
|
||||||
|
ok = !file2.isDirectory() && file2.isFile() && !strcmp(file2.name(), "file2");
|
||||||
|
REQUIRE(ok);
|
||||||
|
ok = dir1.isDirectory() && !dir1.isFile() && !strcmp(dir1.name(), "dir1");
|
||||||
|
REQUIRE(ok);
|
||||||
|
ok = !dir1_file3.isDirectory() && dir1_file3.isFile() && !strcmp(dir1_file3.name(), "file3") &&
|
||||||
|
!strcmp(dir1_file3.fullName(), "dir1/file3");
|
||||||
|
REQUIRE(ok);
|
||||||
|
ok = dir2.isDirectory() && !dir2.isFile() && !strcmp(dir2.name(), "dir2");
|
||||||
|
REQUIRE(ok);
|
||||||
|
ok = dir2_dir3.isDirectory() && !dir2_dir3.isFile() && !strcmp(dir2_dir3.name(), "dir3");
|
||||||
|
REQUIRE(ok);
|
||||||
|
ok = !dir2_dir3_file4.isDirectory() && dir2_dir3_file4.isFile() && !strcmp(dir2_dir3_file4.name(), "file4") &&
|
||||||
|
!strcmp(dir2_dir3_file4.fullName(), "dir2/dir3/file4");
|
||||||
|
REQUIRE(ok);
|
||||||
|
|
||||||
|
REQUIRE(readFileSD("/file1") == "hello");
|
||||||
|
REQUIRE(readFileSD("file2") == "hola");
|
||||||
|
REQUIRE(readFileSD("dir1/file3") == "nihao");
|
||||||
|
REQUIRE(readFileSD("/dir2/dir3/file4") == "bonjour");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,10 @@ function build_sketches_with_platformio()
|
|||||||
echo -e "\n ------------ Skipping $sketch ------------ \n";
|
echo -e "\n ------------ Skipping $sketch ------------ \n";
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
if [[ $(skip_ino $sketch) = 1 ]]; then
|
||||||
|
echo -e "\n ------------ Skipping $sketch ------------ \n";
|
||||||
|
continue
|
||||||
|
fi
|
||||||
local build_cmd="pio ci $sketchdir $build_arg"
|
local build_cmd="pio ci $sketchdir $build_arg"
|
||||||
echo -e "\n ------------ Building $sketch ------------ \n";
|
echo -e "\n ------------ Building $sketch ------------ \n";
|
||||||
echo "$build_cmd"
|
echo "$build_cmd"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user