Unverified Commit 4e50fe86 authored by Earle F. Philhower, III's avatar Earle F. Philhower, III Committed by GitHub

Add LittleFS, SD, and SDFS Filesystems and File:: interface (#49)

Pull in the ESP8266 File/Dir/etc. filesystem and port LittleFS
and SD/SDFS to the RP2040.

See https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html
for more information
parent 1eb48f72
......@@ -7,6 +7,12 @@
[submodule "system/pyserial"]
path = tools/pyserial
url = https://github.com/pyserial/pyserial.git
[submodule "libraries/LittleFS/lib/littlefs"]
path = libraries/LittleFS/lib/littlefs
url = https://github.com/littlefs-project/littlefs.git
[submodule "libraries/SdFat"]
path = libraries/SdFat
url = https://github.com/earlephilhower/ESP8266SdFat.git
[submodule "pico-extras"]
path = pico-extras
url = https://github.com/raspberrypi/pico-extras.git
......@@ -66,7 +66,17 @@ Them hit the upload button and your sketch should upload and run.
In some cases the Pico will encounter a hard hang and its USB port will not respond to the auto-reset request. Should this happen, just
follow the initial procedure of holding the BOOTSEL button down while plugging in the Pico to enter the ROM bootloader.
# Uploading with Picoprobe
# Uploading Filesystem Images
The onboard flash filesystem for the Pico, LittleFS, lets you upload a filesystem image from the sketch directory for your sketch to use. Download the needed plugin from
* https://github.com/earlephilhower/arduino-pico-littlefs-plugin/releases
To install, follow the directions in
* https://github.com/earlephilhower/arduino-pico-littlefs-plugin/blob/master/README.md
For detailed usage information, please check the ESP8266 repo documentation (ignore SPIFFS related notes) available at
* https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html
# Uploading Sketches with Picoprobe
If you have built a Raspberry Pi Picoprobe, you can use OpenOCD to handle your sketch uploads and for debugging with GDB.
Under Windows a local admin user should be able to access the Picoprobe port automatically, but under Linux `udev` must be told about the device and to allow normal users access.
......@@ -88,15 +98,17 @@ The installed tools include a version of OpenOCD (in the pqt-openocd directory)
Relatively stable and very functional, but bug reports and PRs always accepted.
* digitalWrite/Read
* shiftIn/Out
* SPI master (tested using SdFat 2.0 https://github.com/greiman/SdFat ... note that the Pico voltage regulator can't reliably supply enough power for a SD Card so use external power, and adjust the `USE_SIMPLE_LITTLE_ENDIAN` define in `src/sdfat.h` to 0)
* SPI master
* analogWrite/PWM
* tone/noTone
* Wire/I2C Master and Slave (tested using DS3231 https://github.com/rodan/ds3231)
* Wire/I2C Master and Slave
* EEPROM
* USB Serial(ACM) w/automatic reboot-to-UF2 upload)
* Hardware UART
* Servo
* Servo, glitchless
* Overclocking and underclocking from the menus
* analogRead and Pico chip temperature
* Filesystems (LittleFS and SD/SDFS)
* I2S audio output
* printf (i.e. debug) output over USB serial
......@@ -105,13 +117,9 @@ The RP2040 PIO state machines (SMs) are used to generate jitter-free:
* Tones
* I2S Output
# Todo
Some major features I want to add are:
* Installable filesystem support (SD, LittleFS, etc.)
* Updated debug infrastructure
# Tutorials from Across the Web
Here are some links to coverage and additional tutorials for using `arduino-pico`
* The File:: class is taken from the ESP8266. See https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html
* Arduino Support for the Pi Pico available! And how fast is the Pico? - https://youtu.be/-XHh17cuH5E
* Pre-release Adafruit QT Py RP2040 - https://www.youtube.com/watch?v=sfC1msqXX0I
* Adafruit Feather RP2040 running LCD + TMP117 - https://www.youtube.com/watch?v=fKDeqZiIwHg
......
/*
FS.cpp - file system wrapper
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
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 "FS.h"
#include "FSImpl.h"
using namespace fs;
static bool sflags(const char* mode, OpenMode& om, AccessMode& am);
size_t File::write(uint8_t c) {
if (!_p)
return 0;
return _p->write(&c, 1);
}
size_t File::write(const uint8_t *buf, size_t size) {
if (!_p)
return 0;
return _p->write(buf, size);
}
int File::available() {
if (!_p)
return false;
return _p->size() - _p->position();
}
int File::availableForWrite() {
if (!_p)
return false;
return _p->availableForWrite();
}
int File::read() {
if (!_p)
return -1;
uint8_t result;
if (_p->read(&result, 1) != 1) {
return -1;
}
return result;
}
int File::read(uint8_t* buf, size_t size) {
if (!_p)
return 0;
return _p->read(buf, size);
}
int File::peek() {
if (!_p)
return -1;
size_t curPos = _p->position();
int result = read();
seek(curPos, SeekSet);
return result;
}
void File::flush() {
if (!_p)
return;
_p->flush();
}
bool File::seek(uint32_t pos, SeekMode mode) {
if (!_p)
return false;
return _p->seek(pos, mode);
}
size_t File::position() const {
if (!_p)
return 0;
return _p->position();
}
size_t File::size() const {
if (!_p)
return 0;
return _p->size();
}
void File::close() {
if (_p) {
_p->close();
_p = nullptr;
}
}
File::operator bool() const {
return !!_p;
}
bool File::truncate(uint32_t size) {
if (!_p)
return false;
return _p->truncate(size);
}
const char* File::name() const {
if (!_p)
return nullptr;
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 ret;
ret.reserve(size() - position());
char temp[256+1];
int countRead = readBytes(temp, sizeof(temp)-1);
while (countRead > 0)
{
temp[countRead] = 0;
ret += temp;
countRead = readBytes(temp, sizeof(temp)-1);
}
return ret;
}
time_t File::getLastWrite() {
if (!_p)
return 0;
return _p->getLastWrite();
}
time_t File::getCreationTime() {
if (!_p)
return 0;
return _p->getCreationTime();
}
void File::setTimeCallback(time_t (*cb)(void)) {
if (!_p)
return;
_p->setTimeCallback(cb);
_timeCallback = cb;
}
File Dir::openFile(const char* mode) {
if (!_impl) {
return File();
}
OpenMode om;
AccessMode am;
if (!sflags(mode, om, am)) {
DEBUGV("Dir::openFile: invalid mode `%s`\r\n", mode);
return File();
}
File f(_impl->openFile(om, am), _baseFS);
f.setTimeCallback(_timeCallback);
return f;
}
String Dir::fileName() {
if (!_impl) {
return String();
}
return _impl->fileName();
}
time_t Dir::fileTime() {
if (!_impl)
return 0;
return _impl->fileTime();
}
time_t Dir::fileCreationTime() {
if (!_impl)
return 0;
return _impl->fileCreationTime();
}
size_t Dir::fileSize() {
if (!_impl) {
return 0;
}
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() {
if (!_impl) {
return false;
}
return _impl->next();
}
bool Dir::rewind() {
if (!_impl) {
return false;
}
return _impl->rewind();
}
void Dir::setTimeCallback(time_t (*cb)(void)) {
if (!_impl)
return;
_impl->setTimeCallback(cb);
_timeCallback = cb;
}
bool FS::setConfig(const FSConfig &cfg) {
if (!_impl) {
return false;
}
return _impl->setConfig(cfg);
}
bool FS::begin() {
if (!_impl) {
DEBUGV("#error: FS: no implementation");
return false;
}
_impl->setTimeCallback(_timeCallback);
bool ret = _impl->begin();
DEBUGV("%s\n", ret? "": "#error: FS could not start");
return ret;
}
void FS::end() {
if (_impl) {
_impl->end();
}
}
bool FS::gc() {
if (!_impl) {
return false;
}
return _impl->gc();
}
bool FS::check() {
if (!_impl) {
return false;
}
return _impl->check();
}
bool FS::format() {
if (!_impl) {
return false;
}
return _impl->format();
}
bool FS::info(FSInfo& info){
if (!_impl) {
return false;
}
return _impl->info(info);
}
bool FS::info64(FSInfo64& info){
if (!_impl) {
return false;
}
return _impl->info64(info);
}
File FS::open(const String& path, const char* mode) {
return open(path.c_str(), mode);
}
File FS::open(const char* path, const char* mode) {
if (!_impl) {
return File();
}
OpenMode om;
AccessMode am;
if (!sflags(mode, om, am)) {
DEBUGV("FS::open: invalid mode `%s`\r\n", mode);
return File();
}
File f(_impl->open(path, om, am), this);
f.setTimeCallback(_timeCallback);
return f;
}
bool FS::exists(const char* path) {
if (!_impl) {
return false;
}
return _impl->exists(path);
}
bool FS::exists(const String& path) {
return exists(path.c_str());
}
Dir FS::openDir(const char* path) {
if (!_impl) {
return Dir();
}
DirImplPtr p = _impl->openDir(path);
Dir d(p, this);
d.setTimeCallback(_timeCallback);
return d;
}
Dir FS::openDir(const String& path) {
return openDir(path.c_str());
}
bool FS::remove(const char* path) {
if (!_impl) {
return false;
}
return _impl->remove(path);
}
bool FS::remove(const String& path) {
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) {
if (!_impl) {
return false;
}
return _impl->rename(pathFrom, pathTo);
}
bool FS::rename(const String& pathFrom, const String& pathTo) {
return rename(pathFrom.c_str(), pathTo.c_str());
}
time_t FS::getCreationTime() {
if (!_impl) {
return 0;
}
return _impl->getCreationTime();
}
void FS::setTimeCallback(time_t (*cb)(void)) {
if (!_impl)
return;
_impl->setTimeCallback(cb);
_timeCallback = cb;
}
static bool sflags(const char* mode, OpenMode& om, AccessMode& am) {
switch (mode[0]) {
case 'r':
am = AM_READ;
om = OM_DEFAULT;
break;
case 'w':
am = AM_WRITE;
om = (OpenMode) (OM_CREATE | OM_TRUNCATE);
break;
case 'a':
am = AM_WRITE;
om = (OpenMode) (OM_CREATE | OM_APPEND);
break;
default:
return false;
}
switch(mode[1]) {
case '+':
am = (AccessMode) (AM_WRITE | AM_READ);
break;
case 0:
break;
default:
return false;
}
return true;
}
#if defined(FS_FREESTANDING_FUNCTIONS)
/*
TODO: move these functions to public API:
*/
File open(const char* path, const char* mode);
File open(String& path, const char* mode);
Dir openDir(const char* path);
Dir openDir(String& path);
template<>
bool mount<FS>(FS& fs, const char* mountPoint);
/*
*/
struct MountEntry {
FSImplPtr fs;
String path;
MountEntry* next;
};
static MountEntry* s_mounted = nullptr;
template<>
bool mount<FS>(FS& fs, const char* mountPoint) {
FSImplPtr p = fs._impl;
if (!p || !p->mount()) {
DEBUGV("FSImpl mount failed\r\n");
return false;
}
!make sure mountPoint has trailing '/' here
MountEntry* entry = new MountEntry;
entry->fs = p;
entry->path = mountPoint;
entry->next = s_mounted;
s_mounted = entry;
return true;
}
/*
iterate over MountEntries and look for the ones which match the path
*/
File open(const char* path, const char* mode) {
OpenMode om;
AccessMode am;
if (!sflags(mode, om, am)) {
DEBUGV("open: invalid mode `%s`\r\n", mode);
return File();
}
for (MountEntry* entry = s_mounted; entry; entry = entry->next) {
size_t offset = entry->path.length();
if (strstr(path, entry->path.c_str())) {
File result = entry->fs->open(path + offset);
if (result)
return result;
}
}
return File();
}
File open(const String& path, const char* mode) {
return FS::open(path.c_str(), mode);
}
Dir openDir(const String& path) {
return openDir(path.c_str());
}
#endif
/*
FS.h - file system wrapper
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
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
*/
#ifndef FS_H
#define FS_H
#include <memory>
#include <Arduino.h>
#include <../include/time.h> // See issue #6714
class SDClass;
namespace fs {
class File;
class Dir;
class FS;
class FileImpl;
typedef std::shared_ptr<FileImpl> FileImplPtr;
class FSImpl;
typedef std::shared_ptr<FSImpl> FSImplPtr;
class DirImpl;
typedef std::shared_ptr<DirImpl> DirImplPtr;
template <typename Tfs>
bool mount(Tfs& fs, const char* mountPoint);
enum SeekMode {
SeekSet = 0,
SeekCur = 1,
SeekEnd = 2
};
class File : public Stream
{
public:
File(FileImplPtr p = FileImplPtr(), FS *baseFS = nullptr) : _p(p), _fakeDir(nullptr), _baseFS(baseFS) { }
// Print methods:
size_t write(uint8_t) override;
size_t write(const uint8_t *buf, size_t size) override;
int availableForWrite() override;
// Stream methods:
int available() override;
int read() override;
int peek() override;
void flush() override;
size_t readBytes(char *buffer, size_t length) {
return read((uint8_t*)buffer, length);
}
int read(uint8_t* buf, size_t size);
bool seek(uint32_t pos, SeekMode mode);
bool seek(uint32_t pos) {
return seek(pos, SeekSet);
}
size_t position() const;
size_t size() const;
virtual ssize_t streamRemaining() { return (ssize_t)size() - (ssize_t)position(); }
void close();
operator bool() 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
//TODO use stream::send / check read(buf,size) result
template<typename T> size_t write(T &src){
uint8_t obuf[256];
size_t doneLen = 0;
size_t sentLen;
int i;
while (src.available() > sizeof(obuf)){
src.read(obuf, sizeof(obuf));
sentLen = write(obuf, sizeof(obuf));
doneLen = doneLen + sentLen;
if(sentLen != sizeof(obuf)){
return doneLen;
}
}
size_t leftLen = src.available();
src.read(obuf, leftLen);
sentLen = write(obuf, leftLen);
doneLen = doneLen + sentLen;
return doneLen;
}
using Print::write;
void rewindDirectory();
File openNextFile();
String readString();
time_t getLastWrite();
time_t getCreationTime();
void setTimeCallback(time_t (*cb)(void));
protected:
FileImplPtr _p;
time_t (*_timeCallback)(void) = nullptr;
// Arduino SD class emulation
std::shared_ptr<Dir> _fakeDir;
FS *_baseFS;
};
class Dir {
public:
Dir(DirImplPtr impl = DirImplPtr(), FS *baseFS = nullptr): _impl(impl), _baseFS(baseFS) { }
File openFile(const char* mode);
String fileName();
size_t fileSize();
time_t fileTime();
time_t fileCreationTime();
bool isFile() const;
bool isDirectory() const;
bool next();
bool rewind();
void setTimeCallback(time_t (*cb)(void));
protected:
DirImplPtr _impl;
FS *_baseFS;
time_t (*_timeCallback)(void) = nullptr;
};
// Backwards compatible, <4GB filesystem usage
struct FSInfo {
size_t totalBytes;
size_t usedBytes;
size_t blockSize;
size_t pageSize;
size_t maxOpenFiles;
size_t maxPathLength;
};
// Support > 4GB filesystems (SD, etc.)
struct FSInfo64 {
uint64_t totalBytes;
uint64_t usedBytes;
size_t blockSize;
size_t pageSize;
size_t maxOpenFiles;
size_t maxPathLength;
};
class FSConfig
{
public:
static constexpr uint32_t FSId = 0x00000000;
FSConfig(uint32_t type = FSId, bool autoFormat = true) : _type(type), _autoFormat(autoFormat) { }
FSConfig setAutoFormat(bool val = true) {
_autoFormat = val;
return *this;
}
uint32_t _type;
bool _autoFormat;
};
class SPIFFSConfig : public FSConfig
{
public:
static constexpr uint32_t FSId = 0x53504946;
SPIFFSConfig(bool autoFormat = true) : FSConfig(FSId, autoFormat) { }
// Inherit _type and _autoFormat
// nothing yet, enableTime TBD when SPIFFS has metadate
};
class FS
{
public:
FS(FSImplPtr impl) : _impl(impl) { _timeCallback = _defaultTimeCB; }
bool setConfig(const FSConfig &cfg);
bool begin();
void end();
bool format();
bool info(FSInfo& info);
bool info64(FSInfo64& info);
File open(const char* path, const char* mode);
File open(const String& path, const char* mode);
bool exists(const char* path);
bool exists(const String& path);
Dir openDir(const char* path);
Dir openDir(const String& path);
bool remove(const char* path);
bool remove(const String& path);
bool rename(const char* pathFrom, const char* 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);
// Low-level FS routines, not needed by most applications
bool gc();
bool check();
time_t getCreationTime();
void setTimeCallback(time_t (*cb)(void));
friend class ::SDClass; // More of a frenemy, but SD needs internal implementation to get private FAT bits
protected:
FSImplPtr _impl;
FSImplPtr getImpl() { return _impl; }
time_t (*_timeCallback)(void) = nullptr;
static time_t _defaultTimeCB(void) { return time(NULL); }
};
} // namespace fs
extern "C"
{
void close_all_fs(void);
void littlefs_request_end(void);
void spiffs_request_end(void);
}
#ifndef FS_NO_GLOBALS
using fs::FS;
using fs::File;
using fs::Dir;
using fs::SeekMode;
using fs::SeekSet;
using fs::SeekCur;
using fs::SeekEnd;
using fs::FSInfo;
using fs::FSConfig;
using fs::SPIFFSConfig;
#endif //FS_NO_GLOBALS
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SPIFFS)
extern fs::FS SPIFFS __attribute__((deprecated("SPIFFS has been deprecated. Please consider moving to LittleFS or other filesystems.")));
#endif
#endif //FS_H
/*
FSImpl.h - base file system interface
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
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
*/
#ifndef FSIMPL_H
#define FSIMPL_H
#include <stddef.h>
#include <stdint.h>
#include <FS.h>
namespace fs {
class FileImpl {
public:
virtual ~FileImpl() { }
virtual size_t write(const uint8_t *buf, size_t size) = 0;
virtual int read(uint8_t* buf, size_t size) = 0;
virtual void flush() = 0;
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
virtual size_t position() const = 0;
virtual size_t size() const = 0;
virtual int availableForWrite() { return 0; }
virtual bool truncate(uint32_t size) = 0;
virtual void close() = 0;
virtual const char* name() const = 0;
virtual const char* fullName() const = 0;
virtual bool isFile() const = 0;
virtual bool isDirectory() const = 0;
// Filesystems *may* support a timestamp per-file, so allow the user to override with
// their own callback for *this specific* file (as opposed to the FSImpl call of the
// same name. The default implementation simply returns time(null)
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
// Return the last written time for a file. Undefined when called on a writable file
// as the FS is allowed to return either the time of the last write() operation or the
// time present in the filesystem metadata (often the last time the file was closed)
virtual time_t getLastWrite() { return 0; } // Default is to not support timestamps
// Same for creation time.
virtual time_t getCreationTime() { return 0; } // Default is to not support timestamps
protected:
time_t (*_timeCallback)(void) = nullptr;
};
enum OpenMode {
OM_DEFAULT = 0,
OM_CREATE = 1,
OM_APPEND = 2,
OM_TRUNCATE = 4
};
enum AccessMode {
AM_READ = 1,
AM_WRITE = 2,
AM_RW = AM_READ | AM_WRITE
};
class DirImpl {
public:
virtual ~DirImpl() { }
virtual FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) = 0;
virtual const char* fileName() = 0;
virtual size_t fileSize() = 0;
// Return the last written time for a file. Undefined when called on a writable file
// as the FS is allowed to return either the time of the last write() operation or the
// time present in the filesystem metadata (often the last time the file was closed)
virtual time_t fileTime() { return 0; } // By default, FS doesn't report file times
virtual time_t fileCreationTime() { return 0; } // By default, FS doesn't report file times
virtual bool isFile() const = 0;
virtual bool isDirectory() const = 0;
virtual bool next() = 0;
virtual bool rewind() = 0;
// Filesystems *may* support a timestamp per-file, so allow the user to override with
// their own callback for *this specific* file (as opposed to the FSImpl call of the
// same name. The default implementation simply returns time(null)
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
protected:
time_t (*_timeCallback)(void) = nullptr;
};
class FSImpl {
public:
virtual ~FSImpl () { }
virtual bool setConfig(const FSConfig &cfg) = 0;
virtual bool begin() = 0;
virtual void end() = 0;
virtual bool format() = 0;
virtual bool info(FSInfo& info) = 0;
virtual bool info64(FSInfo64& info) = 0;
virtual FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) = 0;
virtual bool exists(const char* path) = 0;
virtual DirImplPtr openDir(const char* path) = 0;
virtual bool rename(const char* pathFrom, const char* pathTo) = 0;
virtual bool remove(const char* path) = 0;
virtual bool mkdir(const char* path) = 0;
virtual bool rmdir(const char* path) = 0;
virtual bool gc() { return true; } // May not be implemented in all file systems.
virtual bool check() { return true; } // May not be implemented in all file systems.
virtual time_t getCreationTime() { return 0; } // May not be implemented in all file systems.
// Filesystems *may* support a timestamp per-file, so allow the user to override with
// their own callback for all files on this FS. The default implementation simply
// returns the present time as reported by time(null)
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
protected:
time_t (*_timeCallback)(void) = nullptr;
};
} // namespace fs
#endif //FSIMPL_H
// Released to the public domain
//
// This sketch will read an uploaded file and increment a counter file
// each time the sketch is booted.
// Be sure to install the Pico LittleFS Data Upload extension for the
// Arduino IDE from:
// https://github.com/earlephilhower/arduino-pico-littlefs-plugin/
// The latest release is available from:
// https://github.com/earlephilhower/arduino-pico-littlefs-plugin/releases
// Before running:
// 1) Select Tools->Flash Size->(some size with a FS/filesystem)
// 2) Use the Tools->Pico Sketch Data Upload tool to transfer the contents of
// the sketch data directory to the Pico
#include <LittleFS.h>
void setup() {
Serial.begin(115200);
delay(5000);
LittleFS.begin();
char buff[32];
int cnt = 1;
File f = LittleFS.open("counts.txt", "r");
if (f) {
bzero(buff, 32);
if (f.read((uint8_t *)buff, 31)) {
sscanf(buff, "%d", &cnt);
Serial.printf("I have been run %d times\n", cnt);
}
f.close();
}
cnt++;
sprintf(buff, "%d\n", cnt);
f = LittleFS.open("counts.txt", "w");
if (f) {
f.write(buff, strlen(buff));
f.close();
}
Serial.println("---------------");
File i = LittleFS.open("file1.txt", "r");
if (i) {
while (i.available()) {
Serial.write(i.read());
}
Serial.println("---------------");
i.close();
}
}
void loop() {
}
Hello!
Welcome to the Raspberry Pi Pico using arduino-pico!
// Simple speed test for filesystem objects
// Released to the public domain by Earle F. Philhower, III
#include <FS.h>
#include <LittleFS.h>
// Choose the filesystem to test
// WARNING: The filesystem will be formatted at the start of the test!
#define TESTFS LittleFS
//#define TESTFS SDFS
// How large of a file to test
#define TESTSIZEKB 256
void DoTest(FS *fs) {
if (!fs->format()) {
Serial.printf("Unable to format(), aborting\n");
return;
}
if (!fs->begin()) {
Serial.printf("Unable to begin(), aborting\n");
return;
}
uint8_t data[256];
for (int i = 0; i < 256; i++) {
data[i] = (uint8_t) i;
}
Serial.printf("Creating %dKB file, may take a while...\n", TESTSIZEKB);
long start = millis();
File f = fs->open("/testwrite.bin", "w");
if (!f) {
Serial.printf("Unable to open file for writing, aborting\n");
return;
}
for (int i = 0; i < TESTSIZEKB; i++) {
for (int j = 0; j < 4; j++) {
f.write(data, 256);
}
}
f.close();
long stop = millis();
Serial.printf("==> Time to write %dKB in 256b chunks = %ld milliseconds\n", TESTSIZEKB, stop - start);
f = fs->open("/testwrite.bin", "r");
Serial.printf("==> Created file size = %d\n", f.size());
f.close();
Serial.printf("Reading %dKB file sequentially in 256b chunks\n", TESTSIZEKB);
start = millis();
f = fs->open("/testwrite.bin", "r");
for (int i = 0; i < TESTSIZEKB; i++) {
for (int j = 0; j < 4; j++) {
f.read(data, 256);
}
}
f.close();
stop = millis();
Serial.printf("==> Time to read %dKB sequentially in 256b chunks = %ld milliseconds = %ld bytes/s\n", TESTSIZEKB, stop - start, TESTSIZEKB * 1024 / (stop - start) * 1000);
Serial.printf("Reading %dKB file MISALIGNED in flash and RAM sequentially in 256b chunks\n", TESTSIZEKB);
start = millis();
f = fs->open("/testwrite.bin", "r");
f.read();
for (int i = 0; i < TESTSIZEKB; i++) {
for (int j = 0; j < 4; j++) {
f.read(data + 1, 256);
}
}
f.close();
stop = millis();
Serial.printf("==> Time to read %dKB sequentially MISALIGNED in flash and RAM in 256b chunks = %ld milliseconds = %ld bytes/s\n", TESTSIZEKB, stop - start, TESTSIZEKB * 1024 / (stop - start) * 1000);
Serial.printf("Reading %dKB file in reverse by 256b chunks\n", TESTSIZEKB);
start = millis();
f = fs->open("/testwrite.bin", "r");
for (int i = 0; i < TESTSIZEKB; i++) {
for (int j = 0; j < 4; j++) {
if (!f.seek(256 + 256 * j * i, SeekEnd)) {
Serial.printf("Unable to seek to %d, aborting\n", -256 - 256 * j * i);
return;
}
if (256 != f.read(data, 256)) {
Serial.printf("Unable to read 256 bytes, aborting\n");
return;
}
}
}
f.close();
stop = millis();
Serial.printf("==> Time to read %dKB in reverse in 256b chunks = %ld milliseconds = %ld bytes/s\n", TESTSIZEKB, stop - start, TESTSIZEKB * 1024 / (stop - start) * 1000);
Serial.printf("Writing 64K file in 1-byte chunks\n");
start = millis();
f = fs->open("/test1b.bin", "w");
for (int i = 0; i < 65536; i++) {
f.write((uint8_t*)&i, 1);
}
f.close();
stop = millis();
Serial.printf("==> Time to write 64KB in 1b chunks = %ld milliseconds = %ld bytes/s\n", stop - start, 65536 / (stop - start) * 1000);
Serial.printf("Reading 64K file in 1-byte chunks\n");
start = millis();
f = fs->open("/test1b.bin", "r");
for (int i = 0; i < 65536; i++) {
char c;
f.read((uint8_t*)&c, 1);
}
f.close();
stop = millis();
Serial.printf("==> Time to read 64KB in 1b chunks = %ld milliseconds = %ld bytes/s\n", stop - start, 65536 / (stop - start) * 1000);
}
void setup() {
Serial.begin(115200);
delay(5000);
Serial.printf("Beginning test\n");
Serial.flush();
DoTest(&TESTFS);
}
void loop() {
delay(10000);
}
Subproject commit 1863dc7883d82bd6ca79faa164b65341064d1c16
name=LittleFS
version=0.1.0
author=Earle F. Philhower, III
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
sentence=Port of LittleFS to RP2040 Arduino
paragraph=Filesystem in the onboard flash, supporting power fail safety and high performance
category=Data Storage
url=https://github.com/earlephilhower/arduino-pico
architectures=rp2040
dot_a_linkage=true
/*
LittleFS.cpp - Wrapper for LittleFS for RP2040
Copyright (c) 2021 Earle F. Philhower, III. All rights reserved.
Based extensively off of the ESP8266 SPIFFS code, which is
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
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 <Arduino.h>
#include <stdlib.h>
#include <algorithm>
#include "LittleFS.h"
#include <hardware/flash.h>
#include <hardware/sync.h>
extern uint8_t _FS_start;
extern uint8_t _FS_end;
namespace littlefs_impl {
FileImplPtr LittleFSImpl::open(const char* path, OpenMode openMode, AccessMode accessMode) {
if (!_mounted) {
DEBUGV("LittleFSImpl::open() called on unmounted FS\n");
return FileImplPtr();
}
if (!path || !path[0]) {
DEBUGV("LittleFSImpl::open() called with invalid filename\n");
return FileImplPtr();
}
if (!LittleFSImpl::pathValid(path)) {
DEBUGV("LittleFSImpl::open() called with too long filename\n");
return FileImplPtr();
}
int flags = _getFlags(openMode, accessMode);
auto fd = std::make_shared<lfs_file_t>();
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 = strchr(pathStr, '/');
while (ptr) {
*ptr = 0;
lfs_mkdir(&_lfs, pathStr);
*ptr = '/';
ptr = strchr(ptr+1, '/');
}
}
free(pathStr);
}
time_t creation = 0;
if (_timeCallback && (openMode & OM_CREATE)) {
// O_CREATE means we *may* make the file, but not if it already exists.
// See if it exists, and only if not update the creation time
int rc = lfs_file_open(&_lfs, fd.get(), path, LFS_O_RDONLY);
if (rc == 0) {
lfs_file_close(&_lfs, fd.get()); // It exists, don't update create time
} else {
creation = _timeCallback(); // File didn't exist or otherwise, so we're going to create this time
}
}
int rc = lfs_file_open(&_lfs, fd.get(), path, flags);
if (rc == LFS_ERR_ISDIR) {
// To support the SD.openNextFile, a null FD indicates to the LittleFSFile this is just
// a directory whose name we are carrying around but which cannot be read or written
return std::make_shared<LittleFSFileImpl>(this, path, nullptr, flags, creation);
} else if (rc == 0) {
lfs_file_sync(&_lfs, fd.get());
return std::make_shared<LittleFSFileImpl>(this, path, fd, flags, creation);
} else {
DEBUGV("LittleFSDirImpl::openFile: rc=%d fd=%p path=`%s` openMode=%d accessMode=%d err=%d\n",
rc, fd.get(), path, openMode, accessMode, rc);
return FileImplPtr();
}
}
DirImplPtr LittleFSImpl::openDir(const char *path) {
if (!_mounted || !path) {
return DirImplPtr();
}
char *pathStr = strdup(path); // Allow edits on our scratch copy
// 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.
lfs_info info;
auto dir = std::make_shared<lfs_dir_t>();
int rc;
const char *filter = "";
if (!pathStr[0]) {
// openDir("") === openDir("/")
rc = lfs_dir_open(&_lfs, dir.get(), "/");
filter = "";
} else if (lfs_stat(&_lfs, pathStr, &info) >= 0) {
if (info.type == LFS_TYPE_DIR) {
// Easy peasy, path specifies an existing dir!
rc = lfs_dir_open(&_lfs, dir.get(), pathStr);
filter = "";
} else {
// This is a file, so open the containing dir
char *ptr = strrchr(pathStr, '/');
if (!ptr) {
// No slashes, open the root dir
rc = lfs_dir_open(&_lfs, dir.get(), "/");
filter = pathStr;
} else {
// We've got slashes, open the dir one up
*ptr = 0; // Remove slash, truncate string
rc = lfs_dir_open(&_lfs, dir.get(), pathStr);
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
rc = lfs_dir_open(&_lfs, dir.get(), "/");
filter = pathStr;
} else {
// We've got slashes, open the dir one up
*ptr = 0; // Remove slash, truncate string
rc = lfs_dir_open(&_lfs, dir.get(), pathStr);
filter = ptr + 1;
}
}
if (rc < 0) {
DEBUGV("LittleFSImpl::openDir: path=`%s` err=%d\n", path, rc);
free(pathStr);
return DirImplPtr();
}
// Skip the . and .. entries
lfs_info dirent;
lfs_dir_read(&_lfs, dir.get(), &dirent);
lfs_dir_read(&_lfs, dir.get(), &dirent);
auto ret = std::make_shared<LittleFSDirImpl>(filter, this, dir, pathStr);
free(pathStr);
return ret;
}
int LittleFSImpl::lfs_flash_read(const struct lfs_config *c,
lfs_block_t block, lfs_off_t off, void *dst, lfs_size_t size) {
LittleFSImpl *me = reinterpret_cast<LittleFSImpl*>(c->context);
// Serial.printf(" READ: %p, %d\n", me->_start + (block * me->_blockSize) + off, size);
memcpy(dst, me->_start + (block * me->_blockSize) + off, size);
return 0;
}
int LittleFSImpl::lfs_flash_prog(const struct lfs_config *c,
lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) {
LittleFSImpl *me = reinterpret_cast<LittleFSImpl*>(c->context);
uint8_t *addr = me->_start + (block * me->_blockSize) + off;
uint32_t save = save_and_disable_interrupts();
// Serial.printf("WRITE: %p, $d\n", (intptr_t)addr - (intptr_t)XIP_BASE, size);
flash_range_program((intptr_t)addr - (intptr_t)XIP_BASE, (const uint8_t *)buffer, size);
restore_interrupts(save);
return 0;
}
int LittleFSImpl::lfs_flash_erase(const struct lfs_config *c, lfs_block_t block) {
LittleFSImpl *me = reinterpret_cast<LittleFSImpl*>(c->context);
uint8_t *addr = me->_start + (block * me->_blockSize);
// Serial.printf("ERASE: %p, %d\n", (intptr_t)addr - (intptr_t)XIP_BASE, me->_blockSize);
uint32_t save = save_and_disable_interrupts();
flash_range_erase((intptr_t)addr - (intptr_t)XIP_BASE, me->_blockSize);
restore_interrupts(save);
return 0;
}
int LittleFSImpl::lfs_flash_sync(const struct lfs_config *c) {
/* NOOP */
(void) c;
return 0;
}
}; // namespace
FS LittleFS = FS(FSImplPtr(new littlefs_impl::LittleFSImpl(&_FS_start, &_FS_end - &_FS_start, 256, 4096, 16)));
/*
LittleFS.h - Filesystem wrapper for LittleFS on the RP2040
Copyright (c) 2019 Earle F. Philhower, III. All rights reserved.
Based heavily off of the SPIFFS equivalent code in the ESP8266 core
"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 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 __LITTLEFS_H
#define __LITTLEFS_H
#include <limits>
#include <FS.h>
#include <FSImpl.h>
#define LFS_NAME_MAX 32
#include "../lib/littlefs/lfs.h"
using namespace fs;
namespace littlefs_impl {
class LittleFSFileImpl;
class LittleFSDirImpl;
class LittleFSConfig : public FSConfig
{
public:
static constexpr uint32_t FSId = 0x4c495454;
LittleFSConfig(bool autoFormat = true) : FSConfig(FSId, autoFormat) { }
};
class LittleFSImpl : public FSImpl
{
public:
LittleFSImpl(uint8_t *start, uint32_t size, uint32_t pageSize, uint32_t blockSize, uint32_t maxOpenFds)
: _start(start) , _size(size) , _pageSize(pageSize) , _blockSize(blockSize) , _maxOpenFds(maxOpenFds),
_mounted(false) {
memset(&_lfs, 0, sizeof(_lfs));
memset(&_lfs_cfg, 0, sizeof(_lfs_cfg));
_lfs_cfg.context = (void*) this;
_lfs_cfg.read = lfs_flash_read;
_lfs_cfg.prog = lfs_flash_prog;
_lfs_cfg.erase = lfs_flash_erase;
_lfs_cfg.sync = lfs_flash_sync;
_lfs_cfg.read_size = 256;
_lfs_cfg.prog_size = 256;
_lfs_cfg.block_size = _blockSize;
_lfs_cfg.block_count =_blockSize? _size / _blockSize: 0;
_lfs_cfg.block_cycles = 16; // TODO - need better explanation
_lfs_cfg.cache_size = 256;
_lfs_cfg.lookahead_size = 256;
_lfs_cfg.read_buffer = nullptr;
_lfs_cfg.prog_buffer = nullptr;
_lfs_cfg.lookahead_buffer = nullptr;
_lfs_cfg.name_max = 0;
_lfs_cfg.file_max = 0;
_lfs_cfg.attr_max = 0;
}
~LittleFSImpl() {
if (_mounted) {
lfs_unmount(&_lfs);
}
}
FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) override;
DirImplPtr openDir(const char *path) override;
bool exists(const char* path) override {
if (!_mounted || !path || !path[0]) {
return false;
}
lfs_info info;
int rc = lfs_stat(&_lfs, path, &info);
return rc == 0;
}
bool rename(const char* pathFrom, const char* pathTo) override {
if (!_mounted || !pathFrom || !pathFrom[0] || !pathTo || !pathTo[0]) {
return false;
}
int rc = lfs_rename(&_lfs, pathFrom, pathTo);
if (rc != 0) {
DEBUGV("lfs_rename: rc=%d, from=`%s`, to=`%s`\n", rc, pathFrom, pathTo);
return false;
}
return true;
}
bool info(FSInfo& info) override {
if (!_mounted) {
return false;
}
info.maxOpenFiles = _maxOpenFds;
info.blockSize = _blockSize;
info.pageSize = _pageSize;
info.maxOpenFiles = _maxOpenFds;
info.maxPathLength = LFS_NAME_MAX;
info.totalBytes = _size;
info.usedBytes = _getUsedBlocks() * _blockSize;
return true;
}
virtual bool info64(FSInfo64& info64) {
FSInfo i;
if (!info(i)) {
return false;
}
info64.blockSize = i.blockSize;
info64.pageSize = i.pageSize;
info64.maxOpenFiles = i.maxOpenFiles;
info64.maxPathLength = i.maxPathLength;
info64.totalBytes = i.totalBytes;
info64.usedBytes = i.usedBytes;
return true;
}
bool remove(const char* path) override {
if (!_mounted || !path || !path[0]) {
return false;
}
int rc = lfs_remove(&_lfs, path);
if (rc != 0) {
DEBUGV("lfs_remove: rc=%d path=`%s`\n", rc, path);
return false;
}
// Now try and remove any empty subdirs this makes, silently
char *pathStr = strdup(path);
if (pathStr) {
char *ptr = strrchr(pathStr, '/');
while (ptr) {
*ptr = 0;
lfs_remove(&_lfs, pathStr); // Don't care if fails if there are files left
ptr = strrchr(pathStr, '/');
}
free(pathStr);
}
return true;
}
bool mkdir(const char* path) override {
if (!_mounted || !path || !path[0]) {
return false;
}
int rc = lfs_mkdir(&_lfs, path);
return (rc==0);
}
bool rmdir(const char* path) override {
return remove(path); // Same call on LittleFS
}
bool setConfig(const FSConfig &cfg) override {
if ((cfg._type != LittleFSConfig::FSId) || _mounted) {
return false;
}
_cfg = *static_cast<const LittleFSConfig *>(&cfg);
return true;
}
bool begin() override {
if (_size <= 0) {
DEBUGV("LittleFS size is <= zero");
return false;
}
if (_tryMount()) {
return true;
}
if (!_cfg._autoFormat || !format()) {
return false;
}
return _tryMount();
}
void end() override {
if (!_mounted) {
return;
}
lfs_unmount(&_lfs);
_mounted = false;
}
bool format() override {
if (_size == 0) {
DEBUGV("lfs size is zero\n");
return false;
}
bool wasMounted = _mounted;
if (_mounted) {
lfs_unmount(&_lfs);
_mounted = false;
}
memset(&_lfs, 0, sizeof(_lfs));
int rc = lfs_format(&_lfs, &_lfs_cfg);
if (rc != 0) {
DEBUGV("lfs_format: rc=%d\n", rc);
return false;
}
if(_timeCallback && _tryMount()) {
// Mounting is required to set attributes
time_t t = _timeCallback();
rc = lfs_setattr(&_lfs, "/", 'c', &t, 8);
if (rc != 0) {
DEBUGV("lfs_format, lfs_setattr 'c': rc=%d\n", rc);
return false;
}
rc = lfs_setattr(&_lfs, "/", 't', &t, 8);
if (rc != 0) {
DEBUGV("lfs_format, lfs_setattr 't': rc=%d\n", rc);
return false;
}
lfs_unmount(&_lfs);
_mounted = false;
}
if (wasMounted) {
return _tryMount();
}
return true;
}
time_t getCreationTime() override {
time_t t;
uint32_t t32b;
if (lfs_getattr(&_lfs, "/", 'c', &t, 8) == 8) {
return t;
} else if (lfs_getattr(&_lfs, "/", 'c', &t32b, 4) == 4) {
return (time_t)t32b;
} else {
return 0;
}
}
protected:
friend class LittleFSFileImpl;
friend class LittleFSDirImpl;
lfs_t* getFS() {
return &_lfs;
}
bool _tryMount() {
if (_mounted) {
lfs_unmount(&_lfs);
_mounted = false;
}
memset(&_lfs, 0, sizeof(_lfs));
int rc = lfs_mount(&_lfs, &_lfs_cfg);
if (rc==0) {
_mounted = true;
}
return _mounted;
}
int _getUsedBlocks() {
if (!_mounted) {
return 0;
}
return lfs_fs_size(&_lfs);
}
static int _getFlags(OpenMode openMode, AccessMode accessMode) {
int mode = 0;
if (openMode & OM_CREATE) {
mode |= LFS_O_CREAT;
}
if (openMode & OM_APPEND) {
mode |= LFS_O_APPEND;
}
if (openMode & OM_TRUNCATE) {
mode |= LFS_O_TRUNC;
}
if (accessMode & AM_READ) {
mode |= LFS_O_RDONLY;
}
if (accessMode & AM_WRITE) {
mode |= LFS_O_WRONLY;
}
return mode;
}
// Check that no components of path beyond max len
static bool pathValid(const char *path) {
while (*path) {
const char *slash = strchr(path, '/');
if (!slash) {
if (strlen(path) >= LFS_NAME_MAX) {
// Terminal filename is too long
return false;
}
break;
}
if ((slash - path) >= LFS_NAME_MAX) {
// This subdir name too long
return false;
}
path = slash + 1;
}
return true;
}
// The actual flash accessing routines
static int lfs_flash_read(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size);
static int lfs_flash_prog(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size);
static int lfs_flash_erase(const struct lfs_config *c, lfs_block_t block);
static int lfs_flash_sync(const struct lfs_config *c);
lfs_t _lfs;
lfs_config _lfs_cfg;
LittleFSConfig _cfg;
uint8_t *_start;
uint32_t _size;
uint32_t _pageSize;
uint32_t _blockSize;
uint32_t _maxOpenFds;
bool _mounted;
};
class LittleFSFileImpl : public FileImpl
{
public:
LittleFSFileImpl(LittleFSImpl* fs, const char *name, std::shared_ptr<lfs_file_t> fd, int flags, time_t creation) : _fs(fs), _fd(fd), _opened(true), _flags(flags), _creation(creation) {
_name = std::shared_ptr<char>(new char[strlen(name) + 1], std::default_delete<char[]>());
strcpy(_name.get(), name);
}
~LittleFSFileImpl() override {
if (_opened) {
close();
}
}
size_t write(const uint8_t *buf, size_t size) override {
if (!_opened || !_fd || !buf) {
return 0;
}
int result = lfs_file_write(_fs->getFS(), _getFD(), (void*) buf, size);
if (result < 0) {
DEBUGV("lfs_write rc=%d\n", result);
return 0;
}
return result;
}
int read(uint8_t* buf, size_t size) override {
if (!_opened || !_fd | !buf) {
return 0;
}
int result = lfs_file_read(_fs->getFS(), _getFD(), (void*) buf, size);
if (result < 0) {
DEBUGV("lfs_read rc=%d\n", result);
return 0;
}
return result;
}
void flush() override {
if (!_opened || !_fd) {
return;
}
int rc = lfs_file_sync(_fs->getFS(), _getFD());
if (rc < 0) {
DEBUGV("lfs_file_sync rc=%d\n", rc);
}
}
bool seek(uint32_t pos, SeekMode mode) override {
if (!_opened || !_fd) {
return false;
}
int32_t offset = static_cast<int32_t>(pos);
if (mode == SeekEnd) {
offset = -offset; // TODO - this seems like its plain wrong vs. POSIX
}
auto lastPos = position();
int rc = lfs_file_seek(_fs->getFS(), _getFD(), offset, (int)mode); // NB. SeekMode === LFS_SEEK_TYPES
if (rc < 0) {
DEBUGV("lfs_file_seek rc=%d\n", rc);
return false;
}
if (position() > size()) {
seek(lastPos, SeekSet); // Pretend the seek() never happened
return false;
}
return true;
}
size_t position() const override {
if (!_opened || !_fd) {
return 0;
}
int result = lfs_file_tell(_fs->getFS(), _getFD());
if (result < 0) {
DEBUGV("lfs_file_tell rc=%d\n", result);
return 0;
}
return result;
}
size_t size() const override {
return (_opened && _fd)? lfs_file_size(_fs->getFS(), _getFD()) : 0;
}
bool truncate(uint32_t size) override {
if (!_opened || !_fd) {
return false;
}
int rc = lfs_file_truncate(_fs->getFS(), _getFD(), size);
if (rc < 0) {
DEBUGV("lfs_file_truncate rc=%d\n", rc);
return false;
}
return true;
}
void close() override {
if (_opened && _fd) {
lfs_file_close(_fs->getFS(), _getFD());
_opened = false;
DEBUGV("lfs_file_close: fd=%p\n", _getFD());
if (_timeCallback && (_flags & LFS_O_WRONLY)) {
// If the file opened with O_CREAT, write the creation time attribute
if (_creation) {
int rc = lfs_setattr(_fs->getFS(), _name.get(), 'c', (const void *)&_creation, sizeof(_creation));
if (rc < 0) {
DEBUGV("Unable to set creation time on '%s' to %d\n", _name.get(), _creation);
}
}
// Add metadata with last write time
time_t now = _timeCallback();
int rc = lfs_setattr(_fs->getFS(), _name.get(), 't', (const void *)&now, sizeof(now));
if (rc < 0) {
DEBUGV("Unable to set last write time on '%s' to %d\n", _name.get(), now);
}
}
}
}
time_t getLastWrite() override {
time_t ftime = 0;
if (_opened && _fd) {
int rc = lfs_getattr(_fs->getFS(), _name.get(), 't', (void *)&ftime, sizeof(ftime));
if (rc != sizeof(ftime))
ftime = 0; // Error, so clear read value
}
return ftime;
}
time_t getCreationTime() override {
time_t ftime = 0;
if (_opened && _fd) {
int rc = lfs_getattr(_fs->getFS(), _name.get(), 'c', (void *)&ftime, sizeof(ftime));
if (rc != sizeof(ftime))
ftime = 0; // Error, so clear read value
}
return ftime;
}
const char* name() const override {
if (!_opened) {
return nullptr;
} else {
const char *p = _name.get();
const char *slash = strrchr(p, '/');
return (slash && slash[1]) ? slash + 1 : p;
}
}
const char* fullName() const override {
return _opened ? _name.get() : nullptr;
}
bool isFile() const override {
if (!_opened || !_fd) {
return false;
}
lfs_info info;
int rc = lfs_stat(_fs->getFS(), fullName(), &info);
return (rc == 0) && (info.type == LFS_TYPE_REG);
}
bool isDirectory() const override {
if (!_opened) {
return false;
} else if (!_fd) {
return true;
}
lfs_info info;
int rc = lfs_stat(_fs->getFS(), fullName(), &info);
return (rc == 0) && (info.type == LFS_TYPE_DIR);
}
protected:
lfs_file_t *_getFD() const {
return _fd.get();
}
LittleFSImpl *_fs;
std::shared_ptr<lfs_file_t> _fd;
std::shared_ptr<char> _name;
bool _opened;
int _flags;
time_t _creation;
};
class LittleFSDirImpl : public DirImpl
{
public:
LittleFSDirImpl(const String& pattern, LittleFSImpl* fs, std::shared_ptr<lfs_dir_t> dir, const char *dirPath = nullptr)
: _pattern(pattern) , _fs(fs) , _dir(dir) , _dirPath(nullptr), _valid(false), _opened(true)
{
memset(&_dirent, 0, sizeof(_dirent));
if (dirPath) {
_dirPath = std::shared_ptr<char>(new char[strlen(dirPath) + 1], std::default_delete<char[]>());
strcpy(_dirPath.get(), dirPath);
}
}
~LittleFSDirImpl() override {
if (_opened) {
lfs_dir_close(_fs->getFS(), _getDir());
}
}
FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) override {
if (!_valid) {
return FileImplPtr();
}
int nameLen = 3; // Slashes, terminator
nameLen += _dirPath.get() ? strlen(_dirPath.get()) : 0;
nameLen += strlen(_dirent.name);
char tmpName[nameLen];
snprintf(tmpName, nameLen, "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get()&&_dirPath.get()[0]?"/":"", _dirent.name);
auto ret = _fs->open((const char *)tmpName, openMode, accessMode);
return ret;
}
const char* fileName() override {
if (!_valid) {
return nullptr;
}
return (const char*) _dirent.name;
}
size_t fileSize() override {
if (!_valid) {
return 0;
}
return _dirent.size;
}
time_t fileTime() override {
time_t t;
int32_t t32b;
// If the attribute is 8-bytes, we're all set
if (_getAttr('t', 8, &t)) {
return t;
} else if (_getAttr('t', 4, &t32b)) {
// If it's 4 bytes silently promote to 64b
return (time_t)t32b;
} else {
// OTW, none present
return 0;
}
}
time_t fileCreationTime() override {
time_t t;
int32_t t32b;
// If the attribute is 8-bytes, we're all set
if (_getAttr('c', 8, &t)) {
return t;
} else if (_getAttr('c', 4, &t32b)) {
// If it's 4 bytes silently promote to 64b
return (time_t)t32b;
} else {
// OTW, none present
return 0;
}
}
bool isFile() const override {
return _valid && (_dirent.type == LFS_TYPE_REG);
}
bool isDirectory() const override {
return _valid && (_dirent.type == LFS_TYPE_DIR);
}
bool rewind() override {
_valid = false;
int rc = lfs_dir_rewind(_fs->getFS(), _getDir());
// Skip the . and .. entries
lfs_info dirent;
lfs_dir_read(_fs->getFS(), _getDir(), &dirent);
lfs_dir_read(_fs->getFS(), _getDir(), &dirent);
return (rc == 0);
}
bool next() override {
const int n = _pattern.length();
bool match;
do {
_dirent.name[0] = 0;
int rc = lfs_dir_read(_fs->getFS(), _getDir(), &_dirent);
_valid = (rc == 1);
match = (!n || !strncmp((const char*) _dirent.name, _pattern.c_str(), n));
} while (_valid && !match);
return _valid;
}
protected:
lfs_dir_t *_getDir() const {
return _dir.get();
}
bool _getAttr(char attr, int len, void *dest) {
if (!_valid || !len || !dest) {
return false;
}
int nameLen = 3; // Slashes, terminator
nameLen += _dirPath.get() ? strlen(_dirPath.get()) : 0;
nameLen += strlen(_dirent.name);
char tmpName[nameLen];
snprintf(tmpName, nameLen, "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get()&&_dirPath.get()[0]?"/":"", _dirent.name);
int rc = lfs_getattr(_fs->getFS(), tmpName, attr, dest, len);
return (rc == len);
}
String _pattern;
LittleFSImpl *_fs;
std::shared_ptr<lfs_dir_t> _dir;
std::shared_ptr<char> _dirPath;
lfs_info _dirent;
bool _valid;
bool _opened;
};
};
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_LITTLEFS)
extern FS LittleFS;
using littlefs_impl::LittleFSConfig;
#endif // ARDUINO
#endif // !defined(__LITTLEFS_H)
// Can't place library in ths src/ directory, Arduino will attempt to build the tests/etc.
// Just have a stub here that redirects to the actual source file
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#define LFS_NAME_MAX 32
#define LFS_NO_DEBUG
#define LFS_NO_WARN
#define LFS_NO_ERROR
#include "../lib/littlefs/lfs.c"
#define LFS_NAME_MAX 32
#define LFS_NO_DEBUG
#define LFS_NO_WARN
#define LFS_NO_ERROR
#include "../lib/littlefs/lfs_util.c"
# 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>
/*
SD card datalogger
This example shows how to log data from three analog sensors
to an SD card using the SD library.
The circuit:
analog sensors on analog ins 0, 1, and 2
SD card attached to SPI bus as follows (Raspberry Pi Pico):
** MISO - pin 0
** MOSI - pin 3
** CS - pin 1
** SCK - pin 2
created 24 Nov 2010
modified 9 Apr 2012
by Tom Igoe
This example code is in the public domain.
*/
#include <SPI.h>
#include <SD.h>
const int chipSelect = 4;
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(115200);
Serial.print("Initializing SD card...");
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
// don't do anything more:
return;
}
Serial.println("card initialized.");
}
void loop() {
// make a string for assembling the data to log:
String dataString = "";
// read three sensors and append to the string:
for (int analogPin = 0; analogPin < 3; analogPin++) {
int sensor = analogRead(analogPin);
dataString += String(sensor);
if (analogPin < 2) {
dataString += ",";
}
}
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
File dataFile = SD.open("datalog.txt", FILE_WRITE);
// if the file is available, write to it:
if (dataFile) {
dataFile.println(dataString);
dataFile.close();
// print to the serial port too:
Serial.println(dataString);
}
// if the file isn't open, pop up an error:
else {
Serial.println("error opening datalog.txt");
}
}
/*
SD card file dump
This example shows how to read a file from the SD card using the
SD library and send it over the serial port.
The circuit:
SD card attached to SPI bus as follows:
** MISO - pin 0
** MOSI - pin 3
** CS - pin 1
** SCK - pin 2
created 22 December 2010
by Limor Fried
modified 9 Apr 2012
by Tom Igoe
This example code is in the public domain.
*/
#include <SPI.h>
#include <SD.h>
const int chipSelect = 4;
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(115200);
Serial.print("Initializing SD card...");
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
// don't do anything more:
return;
}
Serial.println("card initialized.");
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
File dataFile = SD.open("datalog.txt");
// if the file is available, write to it:
if (dataFile) {
while (dataFile.available()) {
Serial.write(dataFile.read());
}
dataFile.close();
}
// if the file isn't open, pop up an error:
else {
Serial.println("error opening datalog.txt");
}
}
void loop() {
}
/*
SD card basic file example
This example shows how to create and destroy an SD card file
The circuit:
SD card attached to SPI bus as follows:
** MISO - pin 0
** MOSI - pin 3
** CS - pin 1
** SCK - pin 2
created Nov 2010
by David A. Mellis
modified 9 Apr 2012
by Tom Igoe
This example code is in the public domain.
*/
#include <SPI.h>
#include <SD.h>
File myFile;
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(115200);
Serial.print("Initializing SD card...");
if (!SD.begin(4)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
if (SD.exists("example.txt")) {
Serial.println("example.txt exists.");
} else {
Serial.println("example.txt doesn't exist.");
}
// open a new file and immediately close it:
Serial.println("Creating example.txt...");
myFile = SD.open("example.txt", FILE_WRITE);
myFile.close();
// Check to see if the file exists:
if (SD.exists("example.txt")) {
Serial.println("example.txt exists.");
} else {
Serial.println("example.txt doesn't exist.");
}
// delete the file:
Serial.println("Removing example.txt...");
SD.remove("example.txt");
if (SD.exists("example.txt")) {
Serial.println("example.txt exists.");
} else {
Serial.println("example.txt doesn't exist.");
}
}
void loop() {
// nothing happens after setup finishes.
}
/*
SD card read/write
This example shows how to read and write data to and from an SD card file
The circuit:
SD card attached to SPI bus as follows:
** MISO - pin 0
** MOSI - pin 3
** CS - pin 1
** SCK - pin 2
created Nov 2010
by David A. Mellis
modified 9 Apr 2012
by Tom Igoe
This example code is in the public domain.
*/
#include <SPI.h>
#include <SD.h>
File myFile;
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(115200);
Serial.print("Initializing SD card...");
if (!SD.begin(4)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
myFile = SD.open("test.txt", FILE_WRITE);
// if the file opened okay, write to it:
if (myFile) {
Serial.print("Writing to test.txt...");
myFile.println("testing 1, 2, 3.");
// close the file:
myFile.close();
Serial.println("done.");
} else {
// if the file didn't open, print an error:
Serial.println("error opening test.txt");
}
// re-open the file for reading:
myFile = SD.open("test.txt");
if (myFile) {
Serial.println("test.txt:");
// read from the file until there's nothing else in it:
while (myFile.available()) {
Serial.write(myFile.read());
}
// close the file:
myFile.close();
} else {
// if the file didn't open, print an error:
Serial.println("error opening test.txt");
}
}
void loop() {
// nothing happens after setup
}
/*
Listfiles
This example shows how print out the files in a
directory on a SD card
The circuit:
SD card attached to SPI bus as follows:
** MISO - pin 0
** MOSI - pin 3
** CS - pin 1
** SCK - pin 2
created Nov 2010
by David A. Mellis
modified 9 Apr 2012
by Tom Igoe
modified 2 Feb 2014
by Scott Fitzgerald
This example code is in the public domain.
*/
#include <SPI.h>
#include <SD.h>
File root;
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(115200);
Serial.print("Initializing SD card...");
if (!SD.begin(SS)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
root = SD.open("/");
printDirectory(root, 0);
Serial.println("done!");
}
void loop() {
// nothing happens after setup finishes.
}
void printDirectory(File dir, int numTabs) {
while (true) {
File entry = dir.openNextFile();
if (! entry) {
// no more files
break;
}
for (uint8_t i = 0; i < numTabs; i++) {
Serial.print('\t');
}
Serial.print(entry.name());
if (entry.isDirectory()) {
Serial.println("/");
printDirectory(entry, numTabs + 1);
} else {
// files have sizes, directories do not
Serial.print("\t\t");
Serial.print(entry.size(), DEC);
time_t cr = entry.getCreationTime();
time_t lw = entry.getLastWrite();
struct tm * tmstruct = localtime(&cr);
Serial.printf("\tCREATION: %d-%02d-%02d %02d:%02d:%02d", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
tmstruct = localtime(&lw);
Serial.printf("\tLAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
}
entry.close();
}
}
#######################################
# Syntax Coloring Map SD
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
SD KEYWORD1 SD
File KEYWORD1 SD
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
exists KEYWORD2
mkdir KEYWORD2
remove KEYWORD2
rmdir KEYWORD2
open KEYWORD2
close KEYWORD2
seek KEYWORD2
position KEYWORD2
size KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
FILE_READ LITERAL1
FILE_WRITE LITERAL1
name=SD(rp2040)
version=2.0.0
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
sentence=Enables reading and writing on SD cards using SDFS
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
url=http://www.github.com/earlephilhower/arduino-pico
architectures=rp2040
dot_a_linkage=true
#include "SD.h"
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
SDClass SD;
#endif
void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*) = nullptr;
/*
SD.h - A thin shim for Arduino ESP8266 Filesystems
Copyright (c) 2019 Earle F. Philhower, III. All rights reserved.
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 __SD_H__
#define __SD_H__
#include <Arduino.h>
#include <FS.h>
#include <SDFS.h>
#undef FILE_READ
#define FILE_READ O_READ
#undef FILE_WRITE
#define FILE_WRITE (O_READ | O_WRITE | O_CREAT | O_APPEND)
class SDClass {
public:
boolean begin(uint8_t csPin, uint32_t cfg = SPI_HALF_SPEED) {
SDFS.setConfig(SDFSConfig(csPin, cfg));
return (boolean)SDFS.begin();
}
void end(bool endSPI = true) {
SDFS.end();
if (endSPI) {
SPI.end();
}
}
File open(const char *filename, int mode = FILE_READ) {
return SDFS.open(filename, getMode(mode));
}
File open(const char *filename, const char *mode) {
return SDFS.open(filename, mode);
}
File open(const String &filename, int mode = FILE_READ) {
return open(filename.c_str(), mode);
}
File open(const String &filename, const char *mode) {
return open(filename.c_str(), mode);
}
boolean exists(const char *filepath) {
return (boolean)SDFS.exists(filepath);
}
boolean exists(const String &filepath) {
return (boolean)SDFS.exists(filepath.c_str());
}
boolean rename(const char* filepathfrom, const char* filepathto) {
return (boolean)SDFS.rename(filepathfrom, filepathto);
}
boolean rename(const String &filepathfrom, const String &filepathto) {
return (boolean)rename(filepathfrom.c_str(), filepathto.c_str());
}
boolean mkdir(const char *filepath) {
return (boolean)SDFS.mkdir(filepath);
}
boolean mkdir(const String &filepath) {
return (boolean)SDFS.mkdir(filepath.c_str());
}
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) {
return (boolean)SDFS.rmdir(filepath);
}
boolean rmdir(const String &filepath) {
return rmdir(filepath.c_str());
}
uint8_t type() {
sdfs::SDFSImpl* sd = static_cast<sdfs::SDFSImpl*>(SDFS.getImpl().get());
return sd->type();
}
uint8_t fatType() {
sdfs::SDFSImpl* sd = static_cast<sdfs::SDFSImpl*>(SDFS.getImpl().get());
return sd->fatType();
}
size_t blocksPerCluster() {
sdfs::SDFSImpl* sd = static_cast<sdfs::SDFSImpl*>(SDFS.getImpl().get());
return sd->blocksPerCluster();
}
size_t totalClusters() {
sdfs::SDFSImpl* sd = static_cast<sdfs::SDFSImpl*>(SDFS.getImpl().get());
return sd->totalClusters();
}
size_t blockSize() {
return 512;
}
size_t totalBlocks() {
return (totalClusters() / blocksPerCluster());
}
size_t clusterSize() {
return blocksPerCluster() * blockSize();
}
size_t size() {
uint64_t sz = size64();
#ifdef DEBUG_ESP_PORT
if (sz > (uint64_t)SIZE_MAX) {
DEBUG_ESP_PORT.printf_P(PSTR("WARNING: SD card size overflow (%lld>= 4GB). Please update source to use size64().\n"), sz);
}
#endif
return (size_t)sz;
}
uint64_t size64() {
return ((uint64_t)clusterSize() * (uint64_t)totalClusters());
}
void setTimeCallback(time_t (*cb)(void)) {
SDFS.setTimeCallback(cb);
}
// Wrapper to allow obsolete datetimecallback use, silently convert to time_t in wrappertimecb
void dateTimeCallback(void (*cb)(uint16_t*, uint16_t*)) {
extern void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*);
__SD__userDateTimeCB = cb;
SDFS.setTimeCallback(wrapperTimeCB);
}
private:
const char *getMode(uint8_t mode) {
bool read = (mode & O_READ) ? true : false;
bool write = (mode & O_WRITE) ? true : false;
bool append = (mode & 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"; }
}
static time_t wrapperTimeCB(void) {
extern void (*__SD__userDateTimeCB)(uint16_t*, uint16_t*);
if (__SD__userDateTimeCB) {
uint16_t d, t;
__SD__userDateTimeCB(&d, &t);
return sdfs::SDFSImpl::FatToTimeT(d, t);
}
return time(nullptr);
}
};
// Expose FatStructs.h helpers for MSDOS date/time for use with dateTimeCallback
static inline uint16_t FAT_DATE(uint16_t year, uint8_t month, uint8_t day) {
return (year - 1980) << 9 | month << 5 | day;
}
static inline uint16_t FAT_YEAR(uint16_t fatDate) {
return 1980 + (fatDate >> 9);
}
static inline uint8_t FAT_MONTH(uint16_t fatDate) {
return (fatDate >> 5) & 0XF;
}
static inline uint8_t FAT_DAY(uint16_t fatDate) {
return fatDate & 0X1F;
}
static inline uint16_t FAT_TIME(uint8_t hour, uint8_t minute, uint8_t second) {
return hour << 11 | minute << 5 | second >> 1;
}
static inline uint8_t FAT_HOUR(uint16_t fatTime) {
return fatTime >> 11;
}
static inline uint8_t FAT_MINUTE(uint16_t fatTime) {
return (fatTime >> 5) & 0X3F;
}
static inline uint8_t FAT_SECOND(uint16_t fatTime) {
return 2*(fatTime & 0X1F);
}
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
extern SDClass SD;
#endif
#endif
name=SDFS
version=0.1.0
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
sentence=FS filesystem for use on SD cards using Bill Greiman's amazing FAT16/FAT32 Arduino library.
paragraph=FS filesystem for use on SD cards using Bill Greiman's amazing FAT16/FAT32 Arduino library.
category=Data Storage
url=https://github.com/earlephilhower/arduino-pico
architectures=rp2040
dot_a_linkage=true
/*
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 <FS.h>
using namespace fs;
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SDFS)
FS SDFS = FS(FSImplPtr(new sdfs::SDFSImpl()));
#endif
namespace sdfs {
// Required to be global because SDFAT doesn't allow a this pointer in it's own time call
time_t (*__sdfs_timeCallback)(void) = nullptr;
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::File32 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::File32>(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::File32 dirFile;
const char *filter = "";
if (!pathStr[0]) {
// openDir("") === openDir("/")
dirFile = _fs.open("/", O_RDONLY);
filter = "";
} else if (_fs.exists(pathStr)) {
dirFile = _fs.open(pathStr, 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("/", O_RDONLY);
filter = pathStr;
} else {
// We've got slashes, open the dir one up
*ptr = 0; // Remove slash, truncare string
dirFile = _fs.open(pathStr, 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("/", O_RDONLY);
filter = pathStr;
} else {
// We've got slashes, open the dir one up
*ptr = 0; // Remove slash, truncare string
dirFile = _fs.open(pathStr, O_RDONLY);
filter = ptr + 1;
}
}
if (!dirFile) {
DEBUGV("SDFSImpl::openDir: path=`%s`\n", path);
return DirImplPtr();
}
auto sharedDir = std::make_shared<sdfat::File32>(dirFile);
auto ret = std::make_shared<SDFSDirImpl>(filter, this, sharedDir, pathStr);
free(pathStr);
return ret;
}
bool SDFSImpl::format() {
if (_mounted) {
return false;
}
sdfat::SdCardFactory cardFactory;
sdfat::SdCard* card = cardFactory.newCard(sdfat::SdSpiConfig(_cfg._csPin, DEDICATED_SPI, _cfg._spiSettings));
if (!card || card->errorCode()) {
return false;
}
sdfat::FatFormatter fatFormatter;
uint8_t *sectorBuffer = new uint8_t[512];
bool ret = fatFormatter.format(card, sectorBuffer, nullptr);
delete[] sectorBuffer;
return ret;
}
}; // namespace sdfs
#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 <SPI.h>
#include <SdFat.h>
#include <FS.h>
using namespace fs;
namespace sdfs {
class SDFSFileImpl;
class SDFSDirImpl;
class SDFSConfig : public FSConfig
{
public:
static constexpr uint32_t FSId = 0x53444653;
SDFSConfig(uint8_t csPin = 4, uint32_t spi = SD_SCK_MHZ(10)) : FSConfig(FSId, false), _csPin(csPin), _part(0), _spiSettings(spi) { }
SDFSConfig setAutoFormat(bool val = true) {
_autoFormat = val;
return *this;
}
SDFSConfig setCSPin(uint8_t pin) {
_csPin = pin;
return *this;
}
SDFSConfig setSPI(uint32_t spi) {
_spiSettings = spi;
return *this;
}
SDFSConfig setPart(uint8_t part) {
_part = part;
return *this;
}
// Inherit _type and _autoFormat
uint8_t _csPin;
uint8_t _part;
uint32_t _spiSettings;
};
class SDFSImpl : public FSImpl
{
public:
SDFSImpl() : _mounted(false)
{
}
FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) override;
bool exists(const char* path) override {
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 info64(FSInfo64& info) override {
if (!_mounted) {
DEBUGV("SDFS::info: FS not mounted\n");
return false;
}
info.maxOpenFiles = 999; // TODO - not valid
info.blockSize = _fs.vol()->sectorsPerCluster() * _fs.vol()->bytesPerSector();
info.pageSize = 0; // TODO ?
info.maxPathLength = 255; // TODO ?
info.totalBytes =_fs.vol()->clusterCount() * info.blockSize;
info.usedBytes = info.totalBytes - (_fs.vol()->freeClusterCount() * _fs.vol()->sectorsPerCluster() * _fs.vol()->bytesPerSector());
return true;
}
bool info(FSInfo& info) override {
FSInfo64 i;
if (!info64(i)) {
return false;
}
info.blockSize = i.blockSize;
info.pageSize = i.pageSize;
info.maxOpenFiles = i.maxOpenFiles;
info.maxPathLength = i.maxPathLength;
#ifdef DEBUG_ESP_PORT
if (i.totalBytes > (uint64_t)SIZE_MAX) {
// This catches both total and used cases, since used must always be < total.
DEBUG_ESP_PORT.printf_P(PSTR("WARNING: SD card size overflow (%lld>= 4GB). Please update source to use info64().\n"), i.totalBytes);
}
#endif
info.totalBytes = (size_t)i.totalBytes;
info.usedBytes = (size_t)i.usedBytes;
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) || _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);
}
sdfat::FsDateTime::setCallback(dateTimeCB);
return _mounted;
}
void end() override {
_mounted = false;
// TODO
}
bool format() override;
// The following are not common FS interfaces, but are needed only to
// support the older SD.h exports
uint8_t type() {
return _fs.card()->type();
}
uint8_t fatType() {
return _fs.vol()->fatType();
}
size_t blocksPerCluster() {
return _fs.vol()->sectorsPerCluster();
}
size_t totalClusters() {
return _fs.vol()->clusterCount();
}
size_t totalBlocks() {
return (totalClusters() / blocksPerCluster());
}
size_t clusterSize() {
return blocksPerCluster() * _fs.vol()->bytesPerSector();
}
size_t size() {
return (clusterSize() * totalClusters());
}
// Helper function, takes FAT and makes standard time_t
static time_t FatToTimeT(uint16_t d, uint16_t t) {
struct tm tiempo;
memset(&tiempo, 0, sizeof(tiempo));
tiempo.tm_sec = (((int)t) << 1) & 0x3e;
tiempo.tm_min = (((int)t) >> 5) & 0x3f;
tiempo.tm_hour = (((int)t) >> 11) & 0x1f;
tiempo.tm_mday = (int)(d & 0x1f);
tiempo.tm_mon = ((int)(d >> 5) & 0x0f) - 1;
tiempo.tm_year = ((int)(d >> 9) & 0x7f) + 80;
tiempo.tm_isdst = -1;
return mktime(&tiempo);
}
virtual void setTimeCallback(time_t (*cb)(void)) override {
extern time_t (*__sdfs_timeCallback)(void);
__sdfs_timeCallback = cb;
}
// Because SdFat has a single, global setting for this we can only use a
// static member of our class to return the time/date.
static void dateTimeCB(uint16_t *dosYear, uint16_t *dosTime) {
time_t now;
extern time_t (*__sdfs_timeCallback)(void);
if (__sdfs_timeCallback) {
now = __sdfs_timeCallback();
} else {
now = time(nullptr);
}
struct tm *tiempo = localtime(&now);
*dosYear = ((tiempo->tm_year - 80) << 9) | ((tiempo->tm_mon + 1) << 5) | tiempo->tm_mday;
*dosTime = (tiempo->tm_hour << 11) | (tiempo->tm_min << 5) | tiempo->tm_sec;
}
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 |= O_CREAT;
}
if (openMode & OM_APPEND) {
mode |= O_AT_END;
}
if (openMode & OM_TRUNCATE) {
mode |= O_TRUNC;
}
if (accessMode & AM_READ) {
mode |= O_READ;
}
if (accessMode & AM_WRITE) {
mode |= O_WRITE;
}
return mode;
}
sdfat::SdFat _fs;
SDFSConfig _cfg;
bool _mounted;
};
class SDFSFileImpl : public FileImpl
{
public:
SDFSFileImpl(SDFSImpl *fs, std::shared_ptr<sdfat::File32> 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();
}
int availableForWrite() override
{
return _opened ? _fd->availableSpaceForWrite() : 0;
}
size_t write(const uint8_t *buf, size_t size) override
{
return _opened ? _fd->write(buf, size) : -1;
}
int read(uint8_t* buf, size_t size) override
{
return _opened ? _fd->read(buf, size) : -1;
}
void flush() override
{
if (_opened) {
_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->isDir() : false;
}
time_t getLastWrite() override {
time_t ftime = 0;
if (_opened && _fd) {
sdfat::DirFat_t tmp;
if (_fd.get()->dirEntry(&tmp)) {
ftime = SDFSImpl::FatToTimeT(*(uint16_t*)tmp.modifyDate, *(uint16_t*)tmp.modifyTime);
}
}
return ftime;
}
time_t getCreationTime() override {
time_t ftime = 0;
if (_opened && _fd) {
sdfat::DirFat_t tmp;
if (_fd.get()->dirEntry(&tmp)) {
ftime = SDFSImpl::FatToTimeT(*(uint16_t*)tmp.createDate, *(uint16_t*)tmp.createTime);
}
}
return ftime;
}
protected:
SDFSImpl* _fs;
std::shared_ptr<sdfat::File32> _fd;
std::shared_ptr<char> _name;
bool _opened;
};
class SDFSDirImpl : public DirImpl
{
public:
SDFSDirImpl(const String& pattern, SDFSImpl* fs, std::shared_ptr<sdfat::File32> 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;
}
time_t fileTime() override
{
if (!_valid) {
return 0;
}
return _time;
}
time_t fileCreationTime() override
{
if (!_valid) {
return 0;
}
return _creation;
}
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::File32 file;
file.openNext(_dir.get(), O_READ);
if (file) {
_valid = 1;
_size = file.fileSize();
_isFile = file.isFile();
_isDirectory = file.isDir();
sdfat::DirFat_t tmp;
if (file.dirEntry(&tmp)) {
_time = SDFSImpl::FatToTimeT(*(uint16_t*)tmp.modifyDate, *(uint16_t*)tmp.modifyTime);
_creation = SDFSImpl::FatToTimeT(*(uint16_t*)tmp.createDate, *(uint16_t*)tmp.createTime);
} else {
_time = 0;
_creation = 0;
}
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::File32> _dir;
bool _valid;
char _lfn[64];
time_t _time;
time_t _creation;
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
Subproject commit 3f50e5547d20854d4dfdc715446758ccc734bec2
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment