Skip to content

Commit a389a99

Browse files
earlephilhowerd-a-v
authored andcommitted
Add LittleFS as an optional filesystem, API compatible w/SPIFFS (but not on-flash-format compatible) (#5511)
* Add LittleFS as internal flash filesystem Adds a LittleFS object which uses the ARMmbed littlefs embedded filesystem, https://github.com/ARMmbed/littlefs, to enable a new filesystem for onboard flash utilizing the exact same API as the existing SPIFFS filesystem. LittleFS is built for low memory systems that are subject to random power losses, is actively supported by the ARMmbed community, supports directories, and seems to be much faster in the large-ish read-mostly applications I use. LittleFS, however, has a larger minimum file allocation unit and does not do static wear levelling. This means that for systems that need many little files (<4K), have small SPIFFS areas (64K), or which have a large static set of files covering the majority of flash coupled with a frequently updated set of other files, it may not perform as well. Simply replace SPIFFS.begin() with LittleFS.begin() in your sketch, use LittleFS.open in place of SPIFFS.open to open files, and everything else just works thanks to the magic of @igrr's File base class. **LITTLEFS FLASH LAYOUT IS INCOMPATIBLE WITH SPIFFS** Since it is a completely different filesystem, you will need to reformat your flash (and lose any data therein) to use it. Tools to build the flash filesystem and upload are at https://github.com/earlephilhower/arduino-esp8266littlefs-plugin and https://github.com/earlephilhower/mklittlefs/ . The mklittlefs tool is installed as part of the Arduino platform installation, automatically. The included example shows a contrived read-mostly example and demonstrates how the same calls work on either SPIFFS.* or LittleFS.* Host tests are also included as part of CI. Directories are fully supported in LittleFS. This means that LittleFS will have a slight difference vs. SPIFFS when you use LittleFS.openDir()/Dir.next(). On SPIFFS dir.next() will return all filesystem entries, including ones in "subdirs" (because in SPIFFS there are no subdirs and "/" is the same as any other character in a filename). On LittleFS, dir.next() will only return entries in the directory specified, not subdirs. So to list files in "/subdir/..." you need to actually openDir("/subdir") and use Dir.next() to parse through just those elements. The returned filenames also only have the filename returned, not full paths. So on a FS with "/a/1", "/a/2" when you do openDir("/a"); dir.next().getName(); you get "1" and "2" and not "/a/1" and "/a/2" like in SPIFFS. This is consistent with POSIX ideas about reading directories and more natural for a FS. Most code will not be affected by this, but if you depend on openDir/Dir.next() you need to be aware of it. Corresponding ::mkdir, ::rmdir, ::isDirectory, ::isFile, ::openNextFile, and ::rewind methods added to Filesystem objects. Documentation has been updated with this and other LittleFS information. Subdirectories are made silently when they do not exist when you try and create a file in a subdir. They are silently removed when the last file in them is deleted. This is consistent with what SPIFFS does but is obviously not normal POSIX behavior. Since there has never been a "FS.mkdir()" method this is the only way to be compatible with legacy SPIFFS code. SPIFFS code has been refactored to pull out common flash_hal_* ops and placed in its own namespace, like LittleFS. * Fix up merge blank line issue * Merge in the FSConfig changs from SDFS PR Enable setConfig for LittleFS as well plys merge the SPIFFS changes done in the SDFS PR. * Fix merge errors * Update to use v2-alpha branch The V2-alpha branch supports small file optimizations which can help increase the utilization of flash when small files are prevalent. It also adds support for metadata, which means we can start adding things like file creation times, if desired (not yet). * V2 of littlefs is now in upstream/master * Update test to support non-creation-ordered files In a directory, the order in which "readNextFile()" will return a name is undefined. SPIFFS may return it in order, but LittleFS does not as of V2. Update the test to look for files by name when doing readNextFile() testing. * Fix LittleFS.truncate implementation * Fix SDFS tests SDFS, SPIFFS, and LittleFS now all share the same common set of tests, greatly increasing the SDFS test coverage. * Update to point to mklittlefs v2 Upgrade mklittlefs to V2 format support * Remove extra FS::write(const char *s) method This was removed in #5861 and erroneously re-introduced here. * Minimize spurious differences from master * Dramatically reduce memory usage Reduce the program and read chunk sizes which impacts performance minimally but reduces per-file RAM usage of 16KB to <1KB. * Add @d-a-v's host emulation for LittleFS * Fix SW Serial library version * Fix free space reporting Thanks to @TD-er for discovering the issue * Update littlefs to latest upstream * Remove sdfat version included by accident * Update SDFAT to include MOCK changes required * Update to include SD.h test of file append
1 parent b551992 commit a389a99

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2113
-511
lines changed

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
[submodule "libraries/SoftwareSerial"]
88
path = libraries/SoftwareSerial
99
url = https://github.com/plerup/espsoftwareserial.git
10+
[submodule "libraries/LittleFS/lib/littlefs"]
11+
path = libraries/LittleFS/lib/littlefs
12+
url = https://github.com/ARMmbed/littlefs.git
1013
[submodule "libraries/ESP8266SdFat"]
1114
path = libraries/ESP8266SdFat
1215
url = https://github.com/earlephilhower/ESP8266SdFat.git

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,5 @@ ESP8266 core files are licensed under LGPL.
143143
[axTLS](http://axtls.sourceforge.net/) library written by Cameron Rich, built from https://github.com/igrr/axtls-8266, is used in this project. It is distributed under [BSD license](https://github.com/igrr/axtls-8266/blob/master/LICENSE).
144144

145145
[BearSSL](https://bearssl.org) library written by Thomas Pornin, built from https://github.com/earlephilhower/bearssl-esp8266, is used in this project. It is distributed under the [MIT License](https://bearssl.org/#legal-details).
146+
147+
[LittleFS](https://github.com/ARMmbed/littlefs) library written by ARM Limited and released under the [BSD 3-clause license](https://github.com/ARMmbed/littlefs/blob/master/LICENSE.md).

cores/esp8266/Esp.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -519,14 +519,14 @@ uint32_t EspClass::getSketchSize() {
519519
return result;
520520
}
521521

522-
extern "C" uint32_t _SPIFFS_start;
522+
extern "C" uint32_t _FS_start;
523523

524524
uint32_t EspClass::getFreeSketchSpace() {
525525

526526
uint32_t usedSize = getSketchSize();
527527
// round one sector up
528528
uint32_t freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
529-
uint32_t freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000;
529+
uint32_t freeSpaceEnd = (uint32_t)&_FS_start - 0x40200000;
530530

531531
#ifdef DEBUG_SERIAL
532532
DEBUG_SERIAL.printf("usedSize=%u freeSpaceStart=%u freeSpaceEnd=%u\r\n", usedSize, freeSpaceStart, freeSpaceEnd);

cores/esp8266/FS.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class File : public Stream
6262
int read() override;
6363
int peek() override;
6464
void flush() override;
65-
size_t readBytes(char *buffer, size_t length) override {
65+
size_t readBytes(char *buffer, size_t length) override {
6666
return read((uint8_t*)buffer, length);
6767
}
6868
size_t read(uint8_t* buf, size_t size);

cores/esp8266/Updater.cpp

+10-10
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ extern "C" {
2424
#include "user_interface.h"
2525
}
2626

27-
extern "C" uint32_t _SPIFFS_start;
27+
extern "C" uint32_t _FS_start;
2828

2929
UpdaterClass::UpdaterClass()
3030
: _async(false)
@@ -87,8 +87,8 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
8787
}
8888

8989
#ifdef DEBUG_UPDATER
90-
if (command == U_SPIFFS) {
91-
DEBUG_UPDATER.println(F("[begin] Update SPIFFS."));
90+
if (command == U_FS) {
91+
DEBUG_UPDATER.println(F("[begin] Update Filesystem."));
9292
}
9393
#endif
9494

@@ -112,7 +112,7 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
112112
//size of current sketch rounded to a sector
113113
size_t currentSketchSize = (ESP.getSketchSize() + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
114114
//address of the end of the space available for sketch and update
115-
uintptr_t updateEndAddress = (uintptr_t)&_SPIFFS_start - 0x40200000;
115+
uintptr_t updateEndAddress = (uintptr_t)&_FS_start - 0x40200000;
116116
//size of the update rounded to a sector
117117
size_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
118118
//address where we will start writing the update
@@ -130,8 +130,8 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
130130
return false;
131131
}
132132
}
133-
else if (command == U_SPIFFS) {
134-
updateStartAddress = (uintptr_t)&_SPIFFS_start - 0x40200000;
133+
else if (command == U_FS) {
134+
updateStartAddress = (uintptr_t)&_FS_start - 0x40200000;
135135
}
136136
else {
137137
// unknown command
@@ -279,8 +279,8 @@ bool UpdaterClass::end(bool evenIfRemaining){
279279
#ifdef DEBUG_UPDATER
280280
DEBUG_UPDATER.printf_P(PSTR("Staged: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
281281
}
282-
else if (_command == U_SPIFFS) {
283-
DEBUG_UPDATER.printf_P(PSTR("SPIFFS: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
282+
else if (_command == U_FS) {
283+
DEBUG_UPDATER.printf_P(PSTR("Filesystem: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
284284
#endif
285285
}
286286

@@ -392,7 +392,7 @@ bool UpdaterClass::_verifyHeader(uint8_t data) {
392392
return false;
393393
}
394394
return true;
395-
} else if(_command == U_SPIFFS) {
395+
} else if(_command == U_FS) {
396396
// no check of SPIFFS possible with first byte.
397397
return true;
398398
}
@@ -426,7 +426,7 @@ bool UpdaterClass::_verifyEnd() {
426426
}
427427

428428
return true;
429-
} else if(_command == U_SPIFFS) {
429+
} else if(_command == U_FS) {
430430
// SPIFFS is already over written checks make no sense any more.
431431
return true;
432432
}

cores/esp8266/Updater.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
#define UPDATE_ERROR_SIGN (12)
2222

2323
#define U_FLASH 0
24-
#define U_SPIFFS 100
24+
#define U_FS 100
2525
#define U_AUTH 200
2626

2727
#ifdef DEBUG_ESP_UPDATER

cores/esp8266/spiffs_hal.cpp renamed to cores/esp8266/flash_hal.cpp

+15-15
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
#include <Arduino.h>
2222
#include <stdlib.h>
2323
#include <algorithm>
24-
#include "spiffs/spiffs.h"
2524
#include "debug.h"
25+
#include "flash_hal.h"
2626

2727
extern "C" {
2828
#include "c_types.h"
@@ -42,10 +42,10 @@ alignedBegin: ^
4242
alignedEnd: ^
4343
*/
4444

45-
int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
45+
int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
4646
optimistic_yield(10000);
4747

48-
uint32_t result = SPIFFS_OK;
48+
uint32_t result = FLASH_HAL_OK;
4949
uint32_t alignedBegin = (addr + 3) & (~3);
5050
uint32_t alignedEnd = (addr + size) & (~3);
5151
if (alignedEnd < alignedBegin) {
@@ -58,7 +58,7 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
5858
if (!ESP.flashRead(alignedBegin - 4, &tmp, 4)) {
5959
DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n",
6060
__LINE__, addr, size, alignedBegin, alignedEnd);
61-
return SPIFFS_ERR_INTERNAL;
61+
return FLASH_HAL_READ_ERROR;
6262
}
6363
memcpy(dst, ((uint8_t*) &tmp) + 4 - nb, nb);
6464
}
@@ -68,7 +68,7 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
6868
alignedEnd - alignedBegin)) {
6969
DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n",
7070
__LINE__, addr, size, alignedBegin, alignedEnd);
71-
return SPIFFS_ERR_INTERNAL;
71+
return FLASH_HAL_READ_ERROR;
7272
}
7373
}
7474

@@ -78,7 +78,7 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
7878
if (!ESP.flashRead(alignedEnd, &tmp, 4)) {
7979
DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n",
8080
__LINE__, addr, size, alignedBegin, alignedEnd);
81-
return SPIFFS_ERR_INTERNAL;
81+
return FLASH_HAL_READ_ERROR;
8282
}
8383

8484
memcpy(dst + size - nb, &tmp, nb);
@@ -99,7 +99,7 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
9999

100100
static const int UNALIGNED_WRITE_BUFFER_SIZE = 512;
101101

102-
int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) {
102+
int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src) {
103103
optimistic_yield(10000);
104104

105105
uint32_t alignedBegin = (addr + 3) & (~3);
@@ -116,7 +116,7 @@ int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) {
116116
if (!ESP.flashWrite(alignedBegin - 4, (uint32_t*) tmp, 4)) {
117117
DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
118118
__LINE__, addr, size, alignedBegin, alignedEnd);
119-
return SPIFFS_ERR_INTERNAL;
119+
return FLASH_HAL_WRITE_ERROR;
120120
}
121121
}
122122

@@ -128,7 +128,7 @@ int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) {
128128
alignedEnd - alignedBegin)) {
129129
DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
130130
__LINE__, addr, size, alignedBegin, alignedEnd);
131-
return SPIFFS_ERR_INTERNAL;
131+
return FLASH_HAL_WRITE_ERROR;
132132
}
133133
}
134134
else {
@@ -140,7 +140,7 @@ int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) {
140140
if (!ESP.flashWrite(alignedBegin, (uint32_t*) buf, willCopy)) {
141141
DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
142142
__LINE__, addr, size, alignedBegin, alignedEnd);
143-
return SPIFFS_ERR_INTERNAL;
143+
return FLASH_HAL_WRITE_ERROR;
144144
}
145145

146146
sizeLeft -= willCopy;
@@ -158,14 +158,14 @@ int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) {
158158
if (!ESP.flashWrite(alignedEnd, &tmp, 4)) {
159159
DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
160160
__LINE__, addr, size, alignedBegin, alignedEnd);
161-
return SPIFFS_ERR_INTERNAL;
161+
return FLASH_HAL_WRITE_ERROR;
162162
}
163163
}
164164

165-
return SPIFFS_OK;
165+
return FLASH_HAL_OK;
166166
}
167167

168-
int32_t spiffs_hal_erase(uint32_t addr, uint32_t size) {
168+
int32_t flash_hal_erase(uint32_t addr, uint32_t size) {
169169
if ((size & (SPI_FLASH_SEC_SIZE - 1)) != 0 ||
170170
(addr & (SPI_FLASH_SEC_SIZE - 1)) != 0) {
171171
DEBUGV("_spif_erase called with addr=%x, size=%d\r\n", addr, size);
@@ -177,8 +177,8 @@ int32_t spiffs_hal_erase(uint32_t addr, uint32_t size) {
177177
optimistic_yield(10000);
178178
if (!ESP.flashEraseSector(sector + i)) {
179179
DEBUGV("_spif_erase addr=%x size=%d i=%d\r\n", addr, size, i);
180-
return SPIFFS_ERR_INTERNAL;
180+
return FLASH_HAL_ERASE_ERROR;
181181
}
182182
}
183-
return SPIFFS_OK;
183+
return FLASH_HAL_OK;
184184
}

cores/esp8266/flash_hal.h

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#ifndef flash_hal_h
2+
#define flash_hal_h
3+
4+
/*
5+
flash_hal.h - API for accessing raw flash for filesystems
6+
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
7+
8+
This code was influenced by NodeMCU and Sming libraries, and first version of
9+
Arduino wrapper written by Hristo Gochkov.
10+
11+
This file is part of the esp8266 core for Arduino environment.
12+
13+
This library is free software; you can redistribute it and/or
14+
modify it under the terms of the GNU Lesser General Public
15+
License as published by the Free Software Foundation; either
16+
version 2.1 of the License, or (at your option) any later version.
17+
18+
This library is distributed in the hope that it will be useful,
19+
but WITHOUT ANY WARRANTY; without even the implied warranty of
20+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21+
Lesser General Public License for more details.
22+
23+
You should have received a copy of the GNU Lesser General Public
24+
License along with this library; if not, write to the Free Software
25+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26+
*/
27+
#ifdef ARDUINO
28+
extern "C" uint32_t _FS_start;
29+
extern "C" uint32_t _FS_end;
30+
extern "C" uint32_t _FS_page;
31+
extern "C" uint32_t _FS_block;
32+
33+
#define FS_PHYS_ADDR ((uint32_t) (&_FS_start) - 0x40200000)
34+
#define FS_PHYS_SIZE ((uint32_t) (&_FS_end) - (uint32_t) (&_FS_start))
35+
#define FS_PHYS_PAGE ((uint32_t) &_FS_page)
36+
#define FS_PHYS_BLOCK ((uint32_t) &_FS_block)
37+
#endif
38+
39+
// Return values of the following functions
40+
#define FLASH_HAL_OK (0)
41+
#define FLASH_HAL_READ_ERROR (-1)
42+
#define FLASH_HAL_WRITE_ERROR (-2)
43+
#define FLASH_HAL_ERASE_ERROR (-3)
44+
45+
extern int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src);
46+
extern int32_t flash_hal_erase(uint32_t addr, uint32_t size);
47+
extern int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst);
48+
49+
#endif // !defined(flash_hal_h)

cores/esp8266/spiffs/spiffs.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ struct spiffs_t;
8484
/* spi read call function type */
8585
typedef s32_t (*spiffs_read)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *dst);
8686
/* spi write call function type */
87-
typedef s32_t (*spiffs_write)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *src);
87+
typedef s32_t (*spiffs_write)(struct spiffs_t *fs, u32_t addr, u32_t size, const u8_t *src);
8888
/* spi erase call function type */
8989
typedef s32_t (*spiffs_erase)(struct spiffs_t *fs, u32_t addr, u32_t size);
9090

@@ -93,7 +93,7 @@ typedef s32_t (*spiffs_erase)(struct spiffs_t *fs, u32_t addr, u32_t size);
9393
/* spi read call function type */
9494
typedef s32_t (*spiffs_read)(u32_t addr, u32_t size, u8_t *dst);
9595
/* spi write call function type */
96-
typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, u8_t *src);
96+
typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, const u8_t *src);
9797
/* spi erase call function type */
9898
typedef s32_t (*spiffs_erase)(u32_t addr, u32_t size);
9999
#endif // SPIFFS_HAL_CALLBACK_EXTRA

cores/esp8266/spiffs_api.cpp

+9-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
using namespace fs;
2727

28+
namespace spiffs_impl {
29+
2830
FileImplPtr SPIFFSImpl::open(const char* path, OpenMode openMode, AccessMode accessMode)
2931
{
3032
if (!isSpiffsFilenameValid(path)) {
@@ -108,6 +110,8 @@ bool isSpiffsFilenameValid(const char* name)
108110
return len > 0 && len < SPIFFS_OBJ_NAME_LEN;
109111
}
110112

113+
}; // namespace
114+
111115
// these symbols should be defined in the linker script for each flash layout
112116
#ifndef CORE_MOCK
113117
#ifdef ARDUINO
@@ -116,11 +120,11 @@ bool isSpiffsFilenameValid(const char* name)
116120
#endif
117121

118122
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SPIFFS)
119-
FS SPIFFS = FS(FSImplPtr(new SPIFFSImpl(
120-
SPIFFS_PHYS_ADDR,
121-
SPIFFS_PHYS_SIZE,
122-
SPIFFS_PHYS_PAGE,
123-
SPIFFS_PHYS_BLOCK,
123+
FS SPIFFS = FS(FSImplPtr(new spiffs_impl::SPIFFSImpl(
124+
FS_PHYS_ADDR,
125+
FS_PHYS_SIZE,
126+
FS_PHYS_PAGE,
127+
FS_PHYS_BLOCK,
124128
SPIFFS_MAX_OPEN_FILES)));
125129
#endif // ARDUINO
126130
#endif // !CORE_MOCK

0 commit comments

Comments
 (0)