diff --git a/.gitmodules b/.gitmodules
index a3ba6c1ffa..ca26e7d93b 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -7,6 +7,9 @@
 [submodule "libraries/SoftwareSerial"]
 	path = libraries/SoftwareSerial
 	url = https://github.com/plerup/espsoftwareserial.git
+[submodule "libraries/LittleFS/lib/littlefs"]
+	path = libraries/LittleFS/lib/littlefs
+	url = https://github.com/ARMmbed/littlefs.git
 [submodule "libraries/ESP8266SdFat"]
 	path = libraries/ESP8266SdFat
 	url = https://github.com/earlephilhower/ESP8266SdFat.git
diff --git a/README.md b/README.md
index a347be7cb5..7079edd3ac 100644
--- a/README.md
+++ b/README.md
@@ -143,3 +143,5 @@ ESP8266 core files are licensed under LGPL.
 [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).
 
 [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).
+
+[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).
diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp
index 23d61d33fa..37c301458b 100644
--- a/cores/esp8266/Esp.cpp
+++ b/cores/esp8266/Esp.cpp
@@ -519,14 +519,14 @@ uint32_t EspClass::getSketchSize() {
     return result;
 }
 
-extern "C" uint32_t _SPIFFS_start;
+extern "C" uint32_t _FS_start;
 
 uint32_t EspClass::getFreeSketchSpace() {
 
     uint32_t usedSize = getSketchSize();
     // round one sector up
     uint32_t freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
-    uint32_t freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000;
+    uint32_t freeSpaceEnd = (uint32_t)&_FS_start - 0x40200000;
 
 #ifdef DEBUG_SERIAL
     DEBUG_SERIAL.printf("usedSize=%u freeSpaceStart=%u freeSpaceEnd=%u\r\n", usedSize, freeSpaceStart, freeSpaceEnd);
diff --git a/cores/esp8266/FS.h b/cores/esp8266/FS.h
index 7bf71692cc..f30483d901 100644
--- a/cores/esp8266/FS.h
+++ b/cores/esp8266/FS.h
@@ -62,7 +62,7 @@ class File : public Stream
     int read() override;
     int peek() override;
     void flush() override;
-    size_t readBytes(char *buffer, size_t length)  override {
+    size_t readBytes(char *buffer, size_t length) override {
         return read((uint8_t*)buffer, length);
     }
     size_t read(uint8_t* buf, size_t size);
diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp
index 9f68541f44..42c308f181 100644
--- a/cores/esp8266/Updater.cpp
+++ b/cores/esp8266/Updater.cpp
@@ -24,7 +24,7 @@ extern "C" {
     #include "user_interface.h"
 }
 
-extern "C" uint32_t _SPIFFS_start;
+extern "C" uint32_t _FS_start;
 
 UpdaterClass::UpdaterClass()
 : _async(false)
@@ -87,8 +87,8 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
   }
   
 #ifdef DEBUG_UPDATER
-  if (command == U_SPIFFS) {
-    DEBUG_UPDATER.println(F("[begin] Update SPIFFS."));
+  if (command == U_FS) {
+    DEBUG_UPDATER.println(F("[begin] Update Filesystem."));
   }
 #endif
 
@@ -112,7 +112,7 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
     //size of current sketch rounded to a sector
     size_t currentSketchSize = (ESP.getSketchSize() + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
     //address of the end of the space available for sketch and update
-    uintptr_t updateEndAddress = (uintptr_t)&_SPIFFS_start - 0x40200000;
+    uintptr_t updateEndAddress = (uintptr_t)&_FS_start - 0x40200000;
     //size of the update rounded to a sector
     size_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
     //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) {
       return false;
     }
   }
-  else if (command == U_SPIFFS) {
-     updateStartAddress = (uintptr_t)&_SPIFFS_start - 0x40200000;
+  else if (command == U_FS) {
+     updateStartAddress = (uintptr_t)&_FS_start - 0x40200000;
   }
   else {
     // unknown command
@@ -279,8 +279,8 @@ bool UpdaterClass::end(bool evenIfRemaining){
 #ifdef DEBUG_UPDATER
     DEBUG_UPDATER.printf_P(PSTR("Staged: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
   }
-  else if (_command == U_SPIFFS) {
-    DEBUG_UPDATER.printf_P(PSTR("SPIFFS: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
+  else if (_command == U_FS) {
+    DEBUG_UPDATER.printf_P(PSTR("Filesystem: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
 #endif
   }
 
@@ -392,7 +392,7 @@ bool UpdaterClass::_verifyHeader(uint8_t data) {
             return false;
         }
         return true;
-    } else if(_command == U_SPIFFS) {
+    } else if(_command == U_FS) {
         // no check of SPIFFS possible with first byte.
         return true;
     }
@@ -426,7 +426,7 @@ bool UpdaterClass::_verifyEnd() {
         }
 
         return true;
-    } else if(_command == U_SPIFFS) {
+    } else if(_command == U_FS) {
         // SPIFFS is already over written checks make no sense any more.
         return true;
     }
diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h
index 8de16c7ed6..e2198e65d6 100644
--- a/cores/esp8266/Updater.h
+++ b/cores/esp8266/Updater.h
@@ -21,7 +21,7 @@
 #define UPDATE_ERROR_SIGN               (12)
 
 #define U_FLASH   0
-#define U_SPIFFS  100
+#define U_FS      100
 #define U_AUTH    200
 
 #ifdef DEBUG_ESP_UPDATER
diff --git a/cores/esp8266/spiffs_hal.cpp b/cores/esp8266/flash_hal.cpp
similarity index 90%
rename from cores/esp8266/spiffs_hal.cpp
rename to cores/esp8266/flash_hal.cpp
index 2d66bd54df..4602f8309a 100644
--- a/cores/esp8266/spiffs_hal.cpp
+++ b/cores/esp8266/flash_hal.cpp
@@ -21,8 +21,8 @@
 #include <Arduino.h>
 #include <stdlib.h>
 #include <algorithm>
-#include "spiffs/spiffs.h"
 #include "debug.h"
+#include "flash_hal.h"
 
 extern "C" {
 #include "c_types.h"
@@ -42,10 +42,10 @@ alignedBegin:            ^
 alignedEnd:                      ^
 */
 
-int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
+int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
     optimistic_yield(10000);
 
-    uint32_t result = SPIFFS_OK;
+    uint32_t result = FLASH_HAL_OK;
     uint32_t alignedBegin = (addr + 3) & (~3);
     uint32_t alignedEnd = (addr + size) & (~3);
     if (alignedEnd < alignedBegin) {
@@ -58,7 +58,7 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
         if (!ESP.flashRead(alignedBegin - 4, &tmp, 4)) {
             DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n",
                 __LINE__, addr, size, alignedBegin, alignedEnd);
-            return SPIFFS_ERR_INTERNAL;
+            return FLASH_HAL_READ_ERROR;
         }
         memcpy(dst, ((uint8_t*) &tmp) + 4 - nb, nb);
     }
@@ -68,7 +68,7 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
                 alignedEnd - alignedBegin)) {
             DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n",
                 __LINE__, addr, size, alignedBegin, alignedEnd);
-            return SPIFFS_ERR_INTERNAL;
+            return FLASH_HAL_READ_ERROR;
         }
     }
 
@@ -78,7 +78,7 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
         if (!ESP.flashRead(alignedEnd, &tmp, 4)) {
             DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n",
                 __LINE__, addr, size, alignedBegin, alignedEnd);
-            return SPIFFS_ERR_INTERNAL;
+            return FLASH_HAL_READ_ERROR;
         }
 
         memcpy(dst + size - nb, &tmp, nb);
@@ -99,7 +99,7 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
 
 static const int UNALIGNED_WRITE_BUFFER_SIZE = 512;
 
-int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) {
+int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src) {
     optimistic_yield(10000);
 
     uint32_t alignedBegin = (addr + 3) & (~3);
@@ -116,7 +116,7 @@ int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) {
         if (!ESP.flashWrite(alignedBegin - 4, (uint32_t*) tmp, 4)) {
             DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
                 __LINE__, addr, size, alignedBegin, alignedEnd);
-            return SPIFFS_ERR_INTERNAL;
+            return FLASH_HAL_WRITE_ERROR;
         }
     }
 
@@ -128,7 +128,7 @@ int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) {
                     alignedEnd - alignedBegin)) {
                 DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
                     __LINE__, addr, size, alignedBegin, alignedEnd);
-                return SPIFFS_ERR_INTERNAL;
+                return FLASH_HAL_WRITE_ERROR;
             }
         }
         else {
@@ -140,7 +140,7 @@ int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) {
                 if (!ESP.flashWrite(alignedBegin, (uint32_t*) buf, willCopy)) {
                     DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
                         __LINE__, addr, size, alignedBegin, alignedEnd);
-                    return SPIFFS_ERR_INTERNAL;
+                    return FLASH_HAL_WRITE_ERROR;
                 }
 
                 sizeLeft -= willCopy;
@@ -158,14 +158,14 @@ int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) {
         if (!ESP.flashWrite(alignedEnd, &tmp, 4)) {
             DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
                 __LINE__, addr, size, alignedBegin, alignedEnd);
-            return SPIFFS_ERR_INTERNAL;
+            return FLASH_HAL_WRITE_ERROR;
         }
     }
 
-    return SPIFFS_OK;
+    return FLASH_HAL_OK;
 }
 
-int32_t spiffs_hal_erase(uint32_t addr, uint32_t size) {
+int32_t flash_hal_erase(uint32_t addr, uint32_t size) {
     if ((size & (SPI_FLASH_SEC_SIZE - 1)) != 0 ||
         (addr & (SPI_FLASH_SEC_SIZE - 1)) != 0) {
         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) {
         optimistic_yield(10000);
         if (!ESP.flashEraseSector(sector + i)) {
             DEBUGV("_spif_erase addr=%x size=%d i=%d\r\n", addr, size, i);
-            return SPIFFS_ERR_INTERNAL;
+            return FLASH_HAL_ERASE_ERROR;
         }
     }
-    return SPIFFS_OK;
+    return FLASH_HAL_OK;
 }
diff --git a/cores/esp8266/flash_hal.h b/cores/esp8266/flash_hal.h
new file mode 100644
index 0000000000..13219bb18f
--- /dev/null
+++ b/cores/esp8266/flash_hal.h
@@ -0,0 +1,49 @@
+#ifndef flash_hal_h
+#define flash_hal_h
+
+/*
+ flash_hal.h - API for accessing raw flash for filesystems
+ 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
+ */
+#ifdef ARDUINO
+extern "C" uint32_t _FS_start;
+extern "C" uint32_t _FS_end;
+extern "C" uint32_t _FS_page;
+extern "C" uint32_t _FS_block;
+
+#define FS_PHYS_ADDR ((uint32_t) (&_FS_start) - 0x40200000)
+#define FS_PHYS_SIZE ((uint32_t) (&_FS_end) - (uint32_t) (&_FS_start))
+#define FS_PHYS_PAGE ((uint32_t) &_FS_page)
+#define FS_PHYS_BLOCK ((uint32_t) &_FS_block)
+#endif
+
+// Return values of the following functions
+#define FLASH_HAL_OK          (0)
+#define FLASH_HAL_READ_ERROR  (-1)
+#define FLASH_HAL_WRITE_ERROR (-2)
+#define FLASH_HAL_ERASE_ERROR (-3)
+
+extern int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src);
+extern int32_t flash_hal_erase(uint32_t addr, uint32_t size);
+extern int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst);
+
+#endif // !defined(flash_hal_h)
diff --git a/cores/esp8266/spiffs/spiffs.h b/cores/esp8266/spiffs/spiffs.h
index 534c3df8bd..e659bf2f37 100644
--- a/cores/esp8266/spiffs/spiffs.h
+++ b/cores/esp8266/spiffs/spiffs.h
@@ -84,7 +84,7 @@ struct spiffs_t;
 /* spi read call function type */
 typedef s32_t (*spiffs_read)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *dst);
 /* spi write call function type */
-typedef s32_t (*spiffs_write)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *src);
+typedef s32_t (*spiffs_write)(struct spiffs_t *fs, u32_t addr, u32_t size, const u8_t *src);
 /* spi erase call function type */
 typedef s32_t (*spiffs_erase)(struct spiffs_t *fs, u32_t addr, u32_t size);
 
@@ -93,7 +93,7 @@ typedef s32_t (*spiffs_erase)(struct spiffs_t *fs, u32_t addr, u32_t size);
 /* spi read call function type */
 typedef s32_t (*spiffs_read)(u32_t addr, u32_t size, u8_t *dst);
 /* spi write call function type */
-typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, u8_t *src);
+typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, const u8_t *src);
 /* spi erase call function type */
 typedef s32_t (*spiffs_erase)(u32_t addr, u32_t size);
 #endif // SPIFFS_HAL_CALLBACK_EXTRA
diff --git a/cores/esp8266/spiffs_api.cpp b/cores/esp8266/spiffs_api.cpp
index f3fcfa2354..833dd3edb0 100644
--- a/cores/esp8266/spiffs_api.cpp
+++ b/cores/esp8266/spiffs_api.cpp
@@ -25,6 +25,8 @@
 
 using namespace fs;
 
+namespace spiffs_impl {
+
 FileImplPtr SPIFFSImpl::open(const char* path, OpenMode openMode, AccessMode accessMode)
 {
     if (!isSpiffsFilenameValid(path)) {
@@ -108,6 +110,8 @@ bool isSpiffsFilenameValid(const char* name)
     return len > 0 && len < SPIFFS_OBJ_NAME_LEN;
 }
 
+}; // namespace
+
 // these symbols should be defined in the linker script for each flash layout
 #ifndef CORE_MOCK
 #ifdef ARDUINO
@@ -116,11 +120,11 @@ bool isSpiffsFilenameValid(const char* name)
 #endif
 
 #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SPIFFS)
-FS SPIFFS = FS(FSImplPtr(new SPIFFSImpl(
-                             SPIFFS_PHYS_ADDR,
-                             SPIFFS_PHYS_SIZE,
-                             SPIFFS_PHYS_PAGE,
-                             SPIFFS_PHYS_BLOCK,
+FS SPIFFS = FS(FSImplPtr(new spiffs_impl::SPIFFSImpl(
+                             FS_PHYS_ADDR,
+                             FS_PHYS_SIZE,
+                             FS_PHYS_PAGE,
+                             FS_PHYS_BLOCK,
                              SPIFFS_MAX_OPEN_FILES)));
 #endif // ARDUINO
 #endif // !CORE_MOCK
diff --git a/cores/esp8266/spiffs_api.h b/cores/esp8266/spiffs_api.h
index 95600a22ad..7312fa0867 100644
--- a/cores/esp8266/spiffs_api.h
+++ b/cores/esp8266/spiffs_api.h
@@ -35,24 +35,11 @@ extern "C" {
 };
 #include "debug.h"
 #include "flash_utils.h"
+#include "flash_hal.h"
 
 using namespace fs;
 
-#ifdef ARDUINO
-extern "C" uint32_t _SPIFFS_start;
-extern "C" uint32_t _SPIFFS_end;
-extern "C" uint32_t _SPIFFS_page;
-extern "C" uint32_t _SPIFFS_block;
-
-#define SPIFFS_PHYS_ADDR ((uint32_t) (&_SPIFFS_start) - 0x40200000)
-#define SPIFFS_PHYS_SIZE ((uint32_t) (&_SPIFFS_end) - (uint32_t) (&_SPIFFS_start))
-#define SPIFFS_PHYS_PAGE ((uint32_t) &_SPIFFS_page)
-#define SPIFFS_PHYS_BLOCK ((uint32_t) &_SPIFFS_block)
-#endif
-
-extern int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src);
-extern int32_t spiffs_hal_erase(uint32_t addr, uint32_t size);
-extern int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst);
+namespace spiffs_impl {
 
 int getSpiffsMode(OpenMode openMode, AccessMode accessMode);
 bool isSpiffsFilenameValid(const char* name);
@@ -95,6 +82,7 @@ class SPIFFSImpl : public FSImpl
         }
         return true;
     }
+
     bool info(FSInfo& info) override
     {
         info.maxOpenFiles = _maxOpenFds;
@@ -150,6 +138,11 @@ class SPIFFSImpl : public FSImpl
 
     bool begin() override
     {
+#if defined(ARDUINO) && !defined(CORE_MOCK)
+        if (&_FS_end <= &_FS_start)
+            return false;
+#endif
+
         if (SPIFFS_mounted(&_fs) != 0) {
             return true;
         }
@@ -160,16 +153,16 @@ class SPIFFSImpl : public FSImpl
         if (_tryMount()) {
             return true;
         }
-	if (_cfg._autoFormat) {
+        if (_cfg._autoFormat) {
             auto rc = SPIFFS_format(&_fs);
             if (rc != SPIFFS_OK) {
                 DEBUGV("SPIFFS_format: rc=%d, err=%d\r\n", rc, _fs.err_code);
                 return false;
             }
             return _tryMount();
-        } else {
-            return false;
         }
+
+        return false;
     }
 
     void end() override
@@ -303,6 +296,17 @@ class SPIFFSImpl : public FSImpl
 
     spiffs _fs;
 
+    // Flash hal wrapper functions to get proper SPIFFS error codes
+    static int32_t spiffs_hal_write(uint32_t addr, uint32_t size, const uint8_t *src) {
+        return flash_hal_write(addr, size, src) == FLASH_HAL_OK ? SPIFFS_OK : SPIFFS_ERR_INTERNAL;
+    }
+    static int32_t spiffs_hal_erase(uint32_t addr, uint32_t size) {
+        return flash_hal_erase(addr, size) == FLASH_HAL_OK ? SPIFFS_OK : SPIFFS_ERR_INTERNAL;
+    }
+    static int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
+        return flash_hal_read(addr, size, dst) == FLASH_HAL_OK ? SPIFFS_OK : SPIFFS_ERR_INTERNAL;
+    }
+
     uint32_t _start;
     uint32_t _size;
     uint32_t _pageSize;
@@ -560,4 +564,6 @@ class SPIFFSDirImpl : public DirImpl
     bool _valid;
 };
 
-#endif//spiffs_api_h
+}; // namespace
+
+#endif //spiffs_api_h
diff --git a/doc/filesystem.rst b/doc/filesystem.rst
index 9e5c0537d3..b7d8b421ef 100644
--- a/doc/filesystem.rst
+++ b/doc/filesystem.rst
@@ -63,10 +63,39 @@ following include to the sketch:
 
     #include "FS.h"
 
-File system limitations
------------------------
+SPIFFS and LittleFS
+-------------------
 
-The filesystem implementation for ESP8266 had to accomodate the
+There are two filesystems for utilizing the onboard flash on the ESP8266:
+SPIFFS and LittleFS.
+
+SPIFFS is the original filesystem and is ideal for space and RAM
+constrained applications that utilize many small files and care
+about static and dynamic wear levelling and don't need true directory
+support.  Filesystem overhead on the flash is minimal as well.
+
+LittleFS is recently added and focuses on higher performance and
+directory support, but has higher filesystem and per-file overhead
+(4K minimum vs. SPIFFS' 256 byte minimum file allocation unit).
+
+They share a compatible API but have incompatible on-flash
+implementations, so it is important to choose one or the per project
+as attempting to mount a SPIFFS volume under LittleFS may result
+in a format operation and definitely will not preserve any files,
+and vice-versa.
+
+The actual ``File`` and ``Dir`` objects returned from either
+filesystem behave in the same manner and documentation is applicable
+to both.  To convert most applications from SPIFFS to LittleFS
+simply requires changing the ``SPIFFS.begin()`` to ``LittleFS.begin()``
+and ``SPIFFS.open()`` to ``LittleFS.open()`` with the rest of the
+code remaining untouched.
+
+
+SPIFFS file system limitations
+------------------------------
+
+The SPIFFS implementation for ESP8266 had to accomodate the
 constraints of the chip, among which its limited RAM.
 `SPIFFS <https://github.com/pellepl/spiffs>`__ was selected because it
 is designed for small systems, but that comes at the cost of some
@@ -100,6 +129,37 @@ For more details on the internals of SPIFFS implementation, see the
 `SPIFFS readme
 file <https://github.com/esp8266/Arduino/blob/master/cores/esp8266/spiffs/README.md>`__.
 
+
+LittleFS file system limitations
+--------------------------------
+
+The LittleFS implementation for the ESP8266 supports filenames of up
+to 31 characters + terminating zero (i.e. ``char filename[32]``), and
+as many subdirectories as space permits.
+
+Filenames are assumed to be in the root directory if no initial "/" is
+present.
+
+Opening files in subdirectories requires specifying the complete path to
+the file (i.e. ``open("/sub/dir/file.txt");``).  Subdirectories are
+automatically created when you attempt to create a file in a subdirectory,
+and when the last file in a subdirectory is removed the subdirectory
+itself is automatically deleted.  This is because there was no ``mkdir()``
+method in the existing SPIFFS filesystem.
+
+Unlike SPIFFS, the actual file descriptors are allocated as requested
+by the application, so in low memory conditions you may not be able to
+open new files.  Conversely, this also means that only file descriptors
+used will actually take space on the heap.
+
+Because there are directories, the ``openDir`` method behaves differently
+than SPIFFS.  Whereas SPIFFS will return files in "subdirectories" when
+you traverse a ``Dir::next()`` (because they really aren't subdirs but
+simply files with "/"s in their names), LittleFS will only return files
+in the specific subdirectory.  This mimics the POSIX behavior for
+directory traversal most C programmers are used to.
+
+
 Uploading files to file system
 ------------------------------
 
@@ -129,8 +189,15 @@ use esptool.py.
    uploading the files into ESP8266 flash file system. When done, IDE
    status bar will display ``SPIFFS Image Uploaded`` message.
 
-File system object (SPIFFS)
----------------------------
+*ESP8266LittleFS* is the equivalent tool for LittleFS.
+
+- Download the tool: https://github.com/earlephilhower/arduino-esp8266littlefs-plugin/releases
+- Install as above
+- To upload a LittleFS filesystem use Tools > ESP8266 LittleFS Data Upload
+
+
+File system object (SPIFFS/LittleFS)
+------------------------------------
 
 setConfig
 ~~~~~~~~~
@@ -156,21 +223,28 @@ begin
 .. code:: cpp
 
     SPIFFS.begin()
+    or LittleFS.begin()
 
-This method mounts SPIFFS file system. It must be called before any
+This method mounts file system. It must be called before any
 other FS APIs are used. Returns *true* if file system was mounted
 successfully, false otherwise.  With no options it will format SPIFFS
 if it is unable to mount it on the first try.
 
+Note that both methods will automatically format the filesystem
+if one is not detected.  This means that if you attempt a
+``SPIFFS.begin()`` on a LittleFS filesystem you will lose all data
+on that filesystem, and vice-versa.
+
 end
 ~~~
 
 .. code:: cpp
 
     SPIFFS.end()
+    or LittleFS.end()
 
-This method unmounts SPIFFS file system. Use this method before updating
-SPIFFS using OTA.
+This method unmounts the file system. Use this method before updating
+the file system using OTA.
 
 format
 ~~~~~~
@@ -178,6 +252,7 @@ format
 .. code:: cpp
 
     SPIFFS.format()
+    or LittleFS.format()
 
 Formats the file system. May be called either before or after calling
 ``begin``. Returns *true* if formatting was successful.
@@ -188,6 +263,7 @@ open
 .. code:: cpp
 
     SPIFFS.open(path, mode)
+    or LittleFS.open(path, mode)
 
 Opens a file. ``path`` should be an absolute path starting with a slash
 (e.g. ``/dir/filename.txt``). ``mode`` is a string specifying access
@@ -234,17 +310,40 @@ exists
 .. code:: cpp
 
     SPIFFS.exists(path)
+    or LittleFS.exists(path)
 
 Returns *true* if a file with given path exists, *false* otherwise.
 
+mkdir
+~~~~~
+
+.. code:: cpp
+
+    LittleFS.mkdir(path)
+
+Returns *true* if the directory creation succeeded, *false* otherwise.
+
+rmdir
+~~~~~
+
+.. code:: cpp
+
+    LittleFS.rmdir(path)
+
+Returns *true* if the directory was successfully removed, *false* otherwise.
+
+
 openDir
 ~~~~~~~
 
 .. code:: cpp
 
     SPIFFS.openDir(path)
+    or LittleFS.openDir(path)
 
 Opens a directory given its absolute path. Returns a *Dir* object.
+Please note the previous discussion on the difference in behavior between
+LittleFS and SPIFFS for this call.
 
 remove
 ~~~~~~
@@ -252,6 +351,7 @@ remove
 .. code:: cpp
 
     SPIFFS.remove(path)
+    or LittleFS.remove(path)
 
 Deletes the file given its absolute path. Returns *true* if file was
 deleted successfully.
@@ -262,6 +362,7 @@ rename
 .. code:: cpp
 
     SPIFFS.rename(pathFrom, pathTo)
+    or LittleFS.rename(pathFrom, pathTo)
 
 Renames file from ``pathFrom`` to ``pathTo``. Paths must be absolute.
 Returns *true* if file was renamed successfully.
@@ -273,6 +374,7 @@ info
 
     FSInfo fs_info;
     SPIFFS.info(fs_info);
+    or LittleFS.info(fs_info);
 
 Fills `FSInfo structure <#filesystem-information-structure>`__ with
 information about the file system. Returns ``true`` is successful,
@@ -294,8 +396,8 @@ Filesystem information structure
 
 This is the structure which may be filled using FS::info method. -
 ``totalBytes`` — total size of useful data on the file system -
-``usedBytes`` — number of bytes used by files - ``blockSize`` — SPIFFS
-block size - ``pageSize`` — SPIFFS logical page size - ``maxOpenFiles``
+``usedBytes`` — number of bytes used by files - ``blockSize`` — filesystem
+block size - ``pageSize`` — filesystem logical page size - ``maxOpenFiles``
 — max number of files which may be open simultaneously -
 ``maxPathLength`` — max file name length (including one byte for zero
 termination)
@@ -304,14 +406,14 @@ Directory object (Dir)
 ----------------------
 
 The purpose of *Dir* object is to iterate over files inside a directory.
-It provides the methods: ``next()``, ``fileName()``, ``fileSize()`` , and
-``openFile(mode)``.
+It provides multiple access methods.
 
 The following example shows how it should be used:
 
 .. code:: cpp
 
     Dir dir = SPIFFS.openDir("/data");
+    // or Dir dir = LittleFS.openDir("/data");
     while (dir.next()) {
         Serial.print(dir.fileName());
         if(dir.fileSize()) {
@@ -339,16 +441,33 @@ fileSize
 Returns the size of the current file pointed to
 by the internal iterator.
 
+isFile
+~~~~~~
+
+Returns *true* if the current file pointed to by
+the internal iterator is a File.
+
+isDirectory
+~~~~~~~~~~~
+
+Returns *true* if the current file pointed to by
+the internal iterator is a Directory.
+
 openFile
 ~~~~~~~~
 
 This method takes *mode* argument which has the same meaning as
-for ``SPIFFS.open()`` function.
+for ``SPIFFS/LittleFS.open()`` function.
+
+rewind
+~~~~~~
+
+Resets the internal pointer to the start of the directory.
 
 File object
 -----------
 
-``SPIFFS.open()`` and ``dir.openFile()`` functions return a *File* object.
+``SPIFFS/LittleFS.open()`` and ``dir.openFile()`` functions return a *File* object.
 This object supports all the functions of *Stream*, so you can use
 ``readBytes``, ``findUntil``, ``parseInt``, ``println``, and all other
 *Stream* methods.
@@ -399,9 +518,42 @@ name
 
     String name = file.name();
 
-Returns file name, as ``const char*``. Convert it to *String* for
+Returns short (no-path) file name, as ``const char*``. Convert it to *String* for
 storage.
 
+fullName
+~~~~~~~~
+
+.. code:: cpp
+
+    // Filesystem:
+    //   testdir/
+    //           file1
+    Dir d = LittleFS.openDir("testdir/");
+    File f = d.openFile("r");
+    // f.name() == "file1", f.fullName() == "testdir/file1"
+
+Returns the full path file name as a ``const char*``.
+
+isFile
+~~~~~~
+
+.. code:: cpp
+
+    bool amIAFile = file.isFile();
+
+Returns *true* if this File points to a real file.
+
+isDirectory
+~~~~~~~~~~~
+
+.. code:: cpp
+
+    bool amIADir = file.isDir();
+
+Returns *true* if this File points to a directory (used for emulation
+of the SD.* interfaces with the ``openNextFile`` method).
+
 close
 ~~~~~
 
@@ -411,3 +563,29 @@ close
 
 Close the file. No other operations should be performed on *File* object
 after ``close`` function was called.
+
+openNextFile  (compatibiity method, not recommended for new code)
+~~~~~~~~~~~~
+
+.. code:: cpp
+
+    File root = LittleFS.open("/");
+    File file1 = root.openNextFile();
+    File files = root.openNextFile();
+
+Opens the next file in the directory pointed to by the File.  Only valid
+when ``File.isDirectory() == true``.
+
+rewindDirectory  (compatibiity method, not recommended for new code)
+~~~~~~~~~~~~~~~
+
+.. code:: cpp
+
+    File root = LittleFS.open("/");
+    File file1 = root.openNextFile();
+    file1.close();
+    root.rewindDirectory();
+    file1 = root.openNextFile(); // Opens first file in dir again
+
+Resets the ``openNextFile`` pointer to the top of the directory.  Only
+valid when ``File.isDirectory() == true``.
diff --git a/libraries/ArduinoOTA/ArduinoOTA.cpp b/libraries/ArduinoOTA/ArduinoOTA.cpp
index ea619d4562..9429518d3f 100644
--- a/libraries/ArduinoOTA/ArduinoOTA.cpp
+++ b/libraries/ArduinoOTA/ArduinoOTA.cpp
@@ -186,7 +186,7 @@ void ArduinoOTAClass::_onRx(){
 
   if (_state == OTA_IDLE) {
     int cmd = parseInt();
-    if (cmd != U_FLASH && cmd != U_SPIFFS)
+    if (cmd != U_FLASH && cmd != U_FS)
       return;
     _ota_ip = _udp_ota->getRemoteAddress();
     _cmd  = cmd;
diff --git a/libraries/ArduinoOTA/ArduinoOTA.h b/libraries/ArduinoOTA/ArduinoOTA.h
index a02dbc485c..1dfcaeed38 100644
--- a/libraries/ArduinoOTA/ArduinoOTA.h
+++ b/libraries/ArduinoOTA/ArduinoOTA.h
@@ -65,7 +65,7 @@ class ArduinoOTAClass
     //Call this in loop() to run the service. Also calls MDNS.update() when begin() or begin(true) is used.
     void handle();
 
-    //Gets update command type after OTA has started. Either U_FLASH or U_SPIFFS
+    //Gets update command type after OTA has started. Either U_FLASH or U_FS
     int getCommand();
 
   private:
diff --git a/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino b/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino
index 214eb82952..ddc5629741 100644
--- a/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino
+++ b/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino
@@ -39,11 +39,11 @@ void setup() {
     String type;
     if (ArduinoOTA.getCommand() == U_FLASH) {
       type = "sketch";
-    } else { // U_SPIFFS
+    } else { // U_FS
       type = "filesystem";
     }
 
-    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
+    // NOTE: if updating FS this would be the place to unmount FS using FS.end()
     Serial.println("Start updating " + type);
   });
   ArduinoOTA.onEnd([]() {
diff --git a/libraries/EEPROM/EEPROM.cpp b/libraries/EEPROM/EEPROM.cpp
index 0ec376abc1..fa1aa3ee06 100644
--- a/libraries/EEPROM/EEPROM.cpp
+++ b/libraries/EEPROM/EEPROM.cpp
@@ -30,7 +30,7 @@ extern "C" {
 #include "spi_flash.h"
 }
 
-extern "C" uint32_t _SPIFFS_end;
+extern "C" uint32_t _FS_end;
 
 EEPROMClass::EEPROMClass(uint32_t sector)
 : _sector(sector)
@@ -41,7 +41,7 @@ EEPROMClass::EEPROMClass(uint32_t sector)
 }
 
 EEPROMClass::EEPROMClass(void)
-: _sector((((uint32_t)&_SPIFFS_end - 0x40200000) / SPI_FLASH_SEC_SIZE))
+: _sector((((uint32_t)&_FS_end - 0x40200000) / SPI_FLASH_SEC_SIZE))
 , _data(0)
 , _size(0)
 , _dirty(false)
diff --git a/libraries/ESP8266SdFat b/libraries/ESP8266SdFat
index f096292195..2499d4e0c6 160000
--- a/libraries/ESP8266SdFat
+++ b/libraries/ESP8266SdFat
@@ -1 +1 @@
-Subproject commit f0962921955e2503243f28de0fdc9ac188177ffb
+Subproject commit 2499d4e0c60db1a67a47532fc44c17d2d5116e4c
diff --git a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp
index bc599f84bf..d3029c453e 100644
--- a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp
+++ b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp
@@ -26,8 +26,8 @@
 #include "ESP8266httpUpdate.h"
 #include <StreamString.h>
 
-extern "C" uint32_t _SPIFFS_start;
-extern "C" uint32_t _SPIFFS_end;
+extern "C" uint32_t _FS_start;
+extern "C" uint32_t _FS_end;
 
 ESP8266HTTPUpdate::ESP8266HTTPUpdate(void)
         : _httpClientTimeout(8000), _followRedirects(false), _ledPin(-1)
@@ -321,7 +321,7 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&
         if(len > 0) {
             bool startUpdate = true;
             if(spiffs) {
-                size_t spiffsSize = ((size_t) &_SPIFFS_end - (size_t) &_SPIFFS_start);
+                size_t spiffsSize = ((size_t) &_FS_end - (size_t) &_FS_start);
                 if(len > (int) spiffsSize) {
                     DEBUG_HTTP_UPDATE("[httpUpdate] spiffsSize to low (%d) needed: %d\n", spiffsSize, len);
                     startUpdate = false;
@@ -348,8 +348,8 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&
                 int command;
 
                 if(spiffs) {
-                    command = U_SPIFFS;
-                    DEBUG_HTTP_UPDATE("[httpUpdate] runUpdate spiffs...\n");
+                    command = U_FS;
+                    DEBUG_HTTP_UPDATE("[httpUpdate] runUpdate filesystem...\n");
                 } else {
                     command = U_FLASH;
                     DEBUG_HTTP_UPDATE("[httpUpdate] runUpdate flash...\n");
diff --git a/libraries/LittleFS/examples/SpeedTest/SpeedTest.ino b/libraries/LittleFS/examples/SpeedTest/SpeedTest.ino
new file mode 100644
index 0000000000..5b7dd453a0
--- /dev/null
+++ b/libraries/LittleFS/examples/SpeedTest/SpeedTest.ino
@@ -0,0 +1,125 @@
+// Simple speed test for filesystem objects
+// Released to the public domain by Earle F. Philhower, III
+
+#include <LittleFS.h>
+
+#define TESTSIZEKB 512
+
+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);
+  Serial.printf("Beginning LittleFS test\n");
+  Serial.flush();
+  DoTest(&LittleFS);
+  Serial.printf("Beginning SPIFFS test\n");
+  Serial.flush();
+  DoTest(&SPIFFS);
+}
+
+void loop() {
+  delay(10000);
+}
diff --git a/libraries/LittleFS/lib/littlefs b/libraries/LittleFS/lib/littlefs
new file mode 160000
index 0000000000..f35fb8c148
--- /dev/null
+++ b/libraries/LittleFS/lib/littlefs
@@ -0,0 +1 @@
+Subproject commit f35fb8c14866a4a4677756f6dbeca78f8a9b4001
diff --git a/libraries/LittleFS/library.properties b/libraries/LittleFS/library.properties
new file mode 100644
index 0000000000..a13e6eb755
--- /dev/null
+++ b/libraries/LittleFS/library.properties
@@ -0,0 +1,10 @@
+name=LittleFS(esp8266)
+version=0.1.0
+author=Earle F. Philhower, III
+maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
+sentence=Port of LittleFS to ESP8266 Arduino
+paragraph=Replacement for SPIFFS to manage a filesystem in the onboard flash, supporting power fail safety and higher performance than SPIFFS at the cost of a lower maximum number of files.
+category=Data Storage
+url=https://github.com/esp8266/Arduino/libraries/LittleFS
+architectures=esp8266
+dot_a_linkage=true
diff --git a/libraries/LittleFS/src/LittleFS.cpp b/libraries/LittleFS/src/LittleFS.cpp
new file mode 100644
index 0000000000..b4aba1970a
--- /dev/null
+++ b/libraries/LittleFS/src/LittleFS.cpp
@@ -0,0 +1,198 @@
+/*
+ LittleFS.cpp - Wrapper for LittleFS for ESP8266
+ Copyright (c_ 2019 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 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 <Arduino.h>
+#include <stdlib.h>
+#include <algorithm>
+#include "LittleFS.h"
+#include "debug.h"
+#include "flash_hal.h"
+
+extern "C" {
+#include "c_types.h"
+#include "spi_flash.h"
+}
+
+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);
+    }
+    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);
+    } else if (rc == 0) {
+        return std::make_shared<LittleFSFileImpl>(this, path, fd);
+    } 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);
+    uint32_t addr = me->_start + (block * me->_blockSize) + off;
+    return flash_hal_read(addr, size, static_cast<uint8_t*>(dst)) == FLASH_HAL_OK ? 0 : -1;
+}
+
+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);
+    uint32_t addr = me->_start + (block * me->_blockSize) + off;
+    const uint8_t *src = reinterpret_cast<const uint8_t *>(buffer);
+    return flash_hal_write(addr, size, static_cast<const uint8_t*>(src)) == FLASH_HAL_OK ? 0 : -1;
+}
+
+int LittleFSImpl::lfs_flash_erase(const struct lfs_config *c, lfs_block_t block) {
+    LittleFSImpl *me = reinterpret_cast<LittleFSImpl*>(c->context);
+    uint32_t addr = me->_start + (block * me->_blockSize);
+    uint32_t size = me->_blockSize;
+    return flash_hal_erase(addr, size) == FLASH_HAL_OK ? 0 : -1;
+}
+
+int LittleFSImpl::lfs_flash_sync(const struct lfs_config *c) {
+    /* NOOP */
+    (void) c;
+    return 0;
+}
+
+
+}; // namespace
+
+// these symbols should be defined in the linker script for each flash layout
+#ifndef CORE_MOCK
+#ifdef ARDUINO
+#ifndef FS_MAX_OPEN_FILES
+#define FS_MAX_OPEN_FILES 5
+#endif
+
+#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_LITTLEFS)
+FS LittleFS = FS(FSImplPtr(new littlefs_impl::LittleFSImpl(FS_PHYS_ADDR, FS_PHYS_SIZE, FS_PHYS_PAGE, FS_PHYS_BLOCK, FS_MAX_OPEN_FILES)));
+#endif
+
+#endif // !CORE_MOCK
+
+
+#endif
diff --git a/libraries/LittleFS/src/LittleFS.h b/libraries/LittleFS/src/LittleFS.h
new file mode 100644
index 0000000000..f81541557c
--- /dev/null
+++ b/libraries/LittleFS/src/LittleFS.h
@@ -0,0 +1,557 @@
+/*
+ LittleFS.h - Filesystem wrapper for LittleFS on the ESP8266
+ 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>
+#include <debug.h>
+#include <flash_utils.h>
+#include <flash_hal.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:
+    LittleFSConfig(bool autoFormat = true) {
+        _type = LittleFSConfig::fsid::FSId;
+        _autoFormat = autoFormat;
+    }
+    enum fsid { FSId = 0x4c495454 };
+};
+
+class LittleFSImpl : public FSImpl
+{
+public:
+    LittleFSImpl(uint32_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 = 64;
+        _lfs_cfg.prog_size = 64;
+        _lfs_cfg.block_size =  _blockSize;
+        _lfs_cfg.block_count = _size / _blockSize;
+	_lfs_cfg.block_cycles = 16; // TODO - need better explanation
+	_lfs_cfg.cache_size = 64;
+        _lfs_cfg.lookahead_size = 64;
+        _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;
+    }
+
+    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::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 (wasMounted) {
+            return _tryMount();
+        }
+
+        return true;
+    }
+
+
+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;
+
+    uint32_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) : _fs(fs), _fd(fd), _opened(true) {
+        _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;
+    }
+
+    size_t 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
+        }
+        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;
+        }
+        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());
+        }
+    }
+
+    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;
+};
+
+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 = (char*)malloc(nameLen);
+        if (!tmpName) {
+            return FileImplPtr();
+        }
+	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);
+        free(tmpName);
+        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;
+    }
+
+    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());
+        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();
+    }
+
+    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)
diff --git a/libraries/LittleFS/src/lfs.c b/libraries/LittleFS/src/lfs.c
new file mode 100644
index 0000000000..62022fc375
--- /dev/null
+++ b/libraries/LittleFS/src/lfs.c
@@ -0,0 +1,10 @@
+// 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"
diff --git a/libraries/LittleFS/src/lfs_util.c b/libraries/LittleFS/src/lfs_util.c
new file mode 100644
index 0000000000..43ada31207
--- /dev/null
+++ b/libraries/LittleFS/src/lfs_util.c
@@ -0,0 +1,6 @@
+#define LFS_NAME_MAX 32
+#define LFS_NO_DEBUG
+#define LFS_NO_WARN
+#define LFS_NO_ERROR
+
+#include "../lib/littlefs/lfs_util.c"
diff --git a/libraries/SD/src/SD.h b/libraries/SD/src/SD.h
index fd4b61e889..bb1c696a58 100644
--- a/libraries/SD/src/SD.h
+++ b/libraries/SD/src/SD.h
@@ -47,10 +47,18 @@ class SDClass {
         return SDFS.open(filename, getMode(mode));
     }
 
+    File open(const char *filename, const char *mode) {
+        return SDFS.open(filename, mode);
+    }
+
     File open(const String &filename, uint8_t 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);
     }
diff --git a/package/package_esp8266com_index.template.json b/package/package_esp8266com_index.template.json
index 2d08d116aa..3b5c58847c 100644
--- a/package/package_esp8266com_index.template.json
+++ b/package/package_esp8266com_index.template.json
@@ -119,6 +119,11 @@
                      "version": "2.5.0-3-20ed2b9",
                      "name": "mkspiffs"
                   },
+                  {
+                     "packager": "esp8266",
+                     "version": "2.5.1-1",
+                     "name": "mklittlefs"
+                  },
                   {
                      "packager": "esp8266",
                      "version": "3.7.2-post1",
@@ -295,6 +300,54 @@
                      "size": "350035"
                   }
                ]
+            },
+            {
+               "version": "2.5.1-1",
+               "name": "mklittlefs",
+               "systems": [
+                  {
+                    "host": "aarch64-linux-gnu",
+                    "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.1-1/aarch64-linux-gnu-mklittlefs-47e8167.tar.gz",
+                    "archiveFileName": "aarch64-linux-gnu-mklittlefs-47e8167.tar.gz",
+                    "checksum": "SHA-256:f5951f0f5c0649992cccbed0146e5ec91dd0a2294f391956bf65c32404a98e96",
+                    "size": "44128"
+                  },
+                  {
+                    "host": "arm-linux-gnueabihf",
+                    "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.1-1/arm-linux-gnueabihf-mklittlefs-47e8167.tar.gz",
+                    "archiveFileName": "arm-linux-gnueabihf-mklittlefs-47e8167.tar.gz",
+                    "checksum": "SHA-256:c5252ce0ae177238efecfa6835950ae12b3cb9dbb7f6d04caedf90afcf0b27b6",
+                    "size": "36590"
+                  },
+                  {
+                    "host": "i686-mingw32",
+                    "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.1-1/i686-w64-mingw32-mklittlefs-47e8167.zip",
+                    "archiveFileName": "i686-w64-mingw32-mklittlefs-47e8167.zip",
+                    "checksum": "SHA-256:85b2dccc9c00ab2747ffbcee8b949c2dc88747f7a7e14cd0bac5b8a8afe19a4b",
+                    "size": "332150"
+                  },
+                  {
+                    "host": "x86_64-apple-darwin",
+                    "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.1-1/x86_64-apple-darwin14-mklittlefs-47e8167.tar.gz",
+                    "archiveFileName": "x86_64-apple-darwin14-mklittlefs-47e8167.tar.gz",
+                    "checksum": "SHA-256:47ba021a929d62b1f5f85f61c935b23fdc3a07bdf01eb3d5f36f064ff4e42154",
+                    "size": "362031"
+                  },
+                  {
+                    "host": "x86_64-pc-linux-gnu",
+                    "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.1-1/x86_64-linux-gnu-mklittlefs-47e8167.tar.gz",
+                    "archiveFileName": "x86_64-linux-gnu-mklittlefs-47e8167.tar.gz",
+                    "checksum": "SHA-256:1e93a62cd550aa2fdb874820ead827880b964bea845a6fa053d12ddb8e39f8f0",
+                    "size": "46226"
+                  },
+                  {
+                    "host": "x86_64-mingw32",
+                    "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.1-1/x86_64-w64-mingw32-mklittlefs-47e8167.zip",
+                    "archiveFileName": "x86_64-w64-mingw32-mklittlefs-47e8167.zip",
+                    "checksum": "SHA-256:0136c76a710c90b0f47d9f01ab4bf46e93daba9935dd498eb9d8b52f23550626",
+                    "size": "344669"
+                  }
+               ]
             }
          ]
       }
diff --git a/platform.txt b/platform.txt
index 2ba6dfefa0..c983055be6 100644
--- a/platform.txt
+++ b/platform.txt
@@ -146,3 +146,7 @@ tools.esptool.upload.network_pattern="{network_cmd}" "{runtime.platform.path}/to
 tools.mkspiffs.cmd=mkspiffs
 tools.mkspiffs.cmd.windows=mkspiffs.exe
 tools.mkspiffs.path={runtime.platform.path}/tools/mkspiffs
+
+tools.mklittlefs.cmd=mklittlefs
+tools.mklittlefs.cmd.windows=mklittlefs.exe
+tools.mklittlefs.path={runtime.platform.path}/tools/mklittlefs
diff --git a/tests/host/Makefile b/tests/host/Makefile
index f4d6d9321a..1c4cc451de 100644
--- a/tests/host/Makefile
+++ b/tests/host/Makefile
@@ -57,6 +57,7 @@ CORE_CPP_FILES := $(addprefix $(CORE_PATH)/,\
 	FS.cpp \
 	spiffs_api.cpp \
 	MD5Builder.cpp \
+	../../libraries/LittleFS/src/LittleFS.cpp \
 	core_esp8266_noniso.cpp \
 	spiffs/spiffs_cache.cpp \
 	spiffs/spiffs_check.cpp \
@@ -81,11 +82,15 @@ CORE_CPP_FILES := $(addprefix $(CORE_PATH)/,\
 	$(LIBRARIES_PATH)/SD/src/SD.cpp
 
 CORE_C_FILES := $(addprefix $(CORE_PATH)/,\
+	../../libraries/LittleFS/src/lfs.c \
+	../../libraries/LittleFS/src/lfs_util.c \
 )
 
 MOCK_CPP_FILES_COMMON := $(addprefix common/,\
 	Arduino.cpp \
+	flash_hal_mock.cpp \
 	spiffs_mock.cpp \
+	littlefs_mock.cpp \
 	sdfs_mock.cpp \
 	WMath.cpp \
 	MockUART.cpp \
@@ -101,6 +106,7 @@ MOCK_CPP_FILES_EMU := $(MOCK_CPP_FILES_COMMON) $(addprefix common/,\
 	ArduinoMain.cpp \
 	ArduinoMainUdp.cpp \
 	ArduinoMainSpiffs.cpp \
+	ArduinoMainLittlefs.cpp \
 	user_interface.cpp \
 )
 
diff --git a/tests/host/common/ArduinoMain.cpp b/tests/host/common/ArduinoMain.cpp
index dba9ab30fb..b0fb164ef0 100644
--- a/tests/host/common/ArduinoMain.cpp
+++ b/tests/host/common/ArduinoMain.cpp
@@ -44,6 +44,7 @@
 bool user_exit = false;
 const char* host_interface = nullptr;
 size_t spiffs_kb = 1024;
+size_t littlefs_kb = 1024;
 bool ignore_sigint = false;
 bool restore_tty = false;
 bool mockdebug = false;
@@ -127,9 +128,10 @@ void help (const char* argv0, int exitcode)
 		"	-f             - no throttle (possibly 100%%CPU)\n"
 		"	-b             - blocking tty/mocked-uart (default: not blocking tty)\n"
 		"	-S             - spiffs size in KBytes (default: %zd)\n"
+		"	-L             - littlefs size in KBytes (default: %zd)\n"
 		"	-v             - mock verbose\n"
 		"                  (negative value will force mismatched size)\n"
-		, argv0, MOCK_PORT_SHIFTER, spiffs_kb);
+		, argv0, MOCK_PORT_SHIFTER, spiffs_kb, littlefs_kb);
 	exit(exitcode);
 }
 
@@ -143,12 +145,14 @@ static struct option options[] =
 	{ "verbose",		no_argument,		NULL, 'v' },
 	{ "interface",		required_argument,	NULL, 'i' },
 	{ "spiffskb",		required_argument,	NULL, 'S' },
+	{ "littlefskb",		required_argument,	NULL, 'L' },
 	{ "portshifter",	required_argument,	NULL, 's' },
 };
 
 void cleanup ()
 {
 	mock_stop_spiffs();
+	mock_stop_littlefs();
 	mock_stop_uart();
 }
 
@@ -204,6 +208,9 @@ int main (int argc, char* const argv [])
 		case 'S':
 			spiffs_kb = atoi(optarg);
 			break;
+		case 'L':
+			littlefs_kb = atoi(optarg);
+			break;
 		case 'b':
 			blocking_uart = true;
 			break;
@@ -226,6 +233,15 @@ int main (int argc, char* const argv [])
 		mock_start_spiffs(name, spiffs_kb);
 	}
 
+	if (littlefs_kb)
+	{
+		String name = argv[0];
+		name += "-littlefs";
+		name += String(littlefs_kb > 0? littlefs_kb: -littlefs_kb, DEC);
+		name += "KB";
+		mock_start_littlefs(name, littlefs_kb);
+	}
+
 	// setup global global_ipv4_netfmt
 	wifi_get_ip_info(0, nullptr);
 
diff --git a/tests/host/common/flash_hal_mock.cpp b/tests/host/common/flash_hal_mock.cpp
new file mode 100644
index 0000000000..5304d7553a
--- /dev/null
+++ b/tests/host/common/flash_hal_mock.cpp
@@ -0,0 +1,36 @@
+/* Emulate the flash read/write HAL */
+
+#include <stdint.h>
+#include <string.h>
+
+extern "C"
+{
+    uint32_t s_phys_addr = 0;
+    uint32_t s_phys_size = 0;
+    uint32_t s_phys_page = 0;
+    uint32_t s_phys_block = 0;
+    uint8_t* s_phys_data = nullptr;
+}
+
+int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
+    memcpy(dst, s_phys_data + addr, size);
+    return 0;
+}
+
+int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src) {
+    memcpy(s_phys_data + addr, src, size);
+    return 0;
+}
+
+int32_t flash_hal_erase(uint32_t addr, uint32_t size) {
+    if ((size & (FLASH_SECTOR_SIZE - 1)) != 0 ||
+        (addr & (FLASH_SECTOR_SIZE - 1)) != 0) {
+        abort();
+    }
+    const uint32_t sector = addr / FLASH_SECTOR_SIZE;
+    const uint32_t sectorCount = size / FLASH_SECTOR_SIZE;
+    for (uint32_t i = 0; i < sectorCount; ++i) {
+        memset(s_phys_data + (sector + i) * FLASH_SECTOR_SIZE, 0xff, FLASH_SECTOR_SIZE);
+    }
+    return 0;
+}
diff --git a/tests/host/common/flash_hal_mock.h b/tests/host/common/flash_hal_mock.h
new file mode 100644
index 0000000000..af5035eaa5
--- /dev/null
+++ b/tests/host/common/flash_hal_mock.h
@@ -0,0 +1,19 @@
+#ifndef flash_hal_mock_h
+#define flash_hal_mock_h
+
+#include <stdint.h>
+
+extern "C"
+{
+    extern uint32_t s_phys_addr;
+    extern uint32_t s_phys_size;
+    extern uint32_t s_phys_page;
+    extern uint32_t s_phys_block;
+    extern uint8_t* s_phys_data;
+}
+
+extern int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst);
+extern int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src);
+extern int32_t flash_hal_erase(uint32_t addr, uint32_t size);
+
+#endif
diff --git a/tests/host/common/littlefs_mock.cpp b/tests/host/common/littlefs_mock.cpp
new file mode 100644
index 0000000000..0f21d7f71c
--- /dev/null
+++ b/tests/host/common/littlefs_mock.cpp
@@ -0,0 +1,132 @@
+/*
+ littlefs_mock.cpp - LittleFS mock for host side testing
+ Copyright © 2019 Earle F. Philhower, III
+
+ Based off spiffs_mock.cpp:
+ Copyright © 2016 Ivan Grokhotkov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+*/
+
+
+#include "littlefs_mock.h"
+#include "spiffs_mock.h"
+#include "spiffs/spiffs.h"
+#include "debug.h"
+#include <flash_utils.h>
+#include <stdlib.h>
+#include <LittleFS.h>
+
+#include <spiffs_api.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "flash_hal_mock.h"
+
+#define LITTLEFS_FILE_NAME "littlefs.bin"
+
+FS LittleFS(nullptr);
+
+LittleFSMock::LittleFSMock(ssize_t fs_size, size_t fs_block, size_t fs_page, const String& storage)
+{
+    m_storage = storage;
+    if ((m_overwrite = (fs_size < 0)))
+        fs_size = -fs_size;
+
+    fprintf(stderr, "LittleFS: %zd bytes\n", fs_size);
+
+    m_fs.resize(fs_size, 0xff);
+    s_phys_addr  = 0;
+    s_phys_size  = static_cast<uint32_t>(fs_size);
+    s_phys_page  = static_cast<uint32_t>(fs_page);
+    s_phys_block = static_cast<uint32_t>(fs_block);
+    s_phys_data  = m_fs.data();
+    reset();
+}
+
+void LittleFSMock::reset()
+{
+    LittleFS = FS(FSImplPtr(new littlefs_impl::LittleFSImpl(0, s_phys_size, s_phys_page, s_phys_block, 5)));
+    load();
+}
+
+LittleFSMock::~LittleFSMock()
+{
+    save();
+    s_phys_addr  = 0;
+    s_phys_size  = 0;
+    s_phys_page  = 0;
+    s_phys_block = 0;
+    s_phys_data  = nullptr;
+    m_fs.resize(0);
+    LittleFS = FS(FSImplPtr(nullptr));
+}
+
+void LittleFSMock::load ()
+{
+    if (!m_fs.size() || !m_storage.length())
+        return;
+    
+    int fs = ::open(m_storage.c_str(), O_RDONLY);
+    if (fs == -1)
+    {
+        fprintf(stderr, "LittleFS: loading '%s': %s\n", m_storage.c_str(), strerror(errno));
+        return;
+    }
+    
+    off_t flen = lseek(fs, 0, SEEK_END);
+    if (flen == (off_t)-1)
+    {
+        fprintf(stderr, "LittleFS: checking size of '%s': %s\n", m_storage.c_str(), strerror(errno));
+        return;
+    }
+    lseek(fs, 0, SEEK_SET);
+    
+    if (flen != (off_t)m_fs.size())
+    {
+        fprintf(stderr, "LittleFS: size of '%s': %d does not match requested size %zd\n", m_storage.c_str(), (int)flen, m_fs.size());
+        if (!m_overwrite)
+        {
+            fprintf(stderr, "LittleFS: aborting at user request\n");
+            exit(1);
+        }
+        fprintf(stderr, "LittleFS: continuing without loading at user request, '%s' will be overwritten\n", m_storage.c_str());
+    }
+    else
+    {
+        fprintf(stderr, "LittleFS: loading %zi bytes from '%s'\n", m_fs.size(), m_storage.c_str());
+        ssize_t r = ::read(fs, m_fs.data(), m_fs.size());
+        if (r != (ssize_t)m_fs.size())
+            fprintf(stderr, "LittleFS: reading %zi bytes: returned %zd: %s\n", m_fs.size(), r, strerror(errno));
+    }
+    ::close(fs);
+}
+
+void LittleFSMock::save ()
+{
+    if (!m_fs.size() || !m_storage.length())
+        return;
+
+    int fs = ::open(m_storage.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0644);
+    if (fs == -1)
+    {
+        fprintf(stderr, "LittleFS: saving '%s': %s\n", m_storage.c_str(), strerror(errno));
+        return;
+    }
+    fprintf(stderr, "LittleFS: saving %zi bytes to '%s'\n", m_fs.size(), m_storage.c_str());
+
+    if (::write(fs, m_fs.data(), m_fs.size()) != (ssize_t)m_fs.size())
+        fprintf(stderr, "LittleFS: writing %zi bytes: %s\n", m_fs.size(), strerror(errno));
+    if (::close(fs) == -1)
+        fprintf(stderr, "LittleFS: closing %s: %s\n", m_storage.c_str(), strerror(errno));
+}
diff --git a/tests/host/common/littlefs_mock.h b/tests/host/common/littlefs_mock.h
new file mode 100644
index 0000000000..0905fa9322
--- /dev/null
+++ b/tests/host/common/littlefs_mock.h
@@ -0,0 +1,48 @@
+/*
+ littlefs_mock.h - LittleFS HAL mock for host side testing
+ Copyright © 2019 Earle F. Philhower, III
+
+ Based on spiffs_mock:
+ Copyright © 2016 Ivan Grokhotkov
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+*/
+
+#ifndef littlefs_mock_hpp
+#define littlefs_mock_hpp
+
+#include <stdint.h>
+#include <stddef.h>
+#include <vector>
+#include <FS.h>
+#include "flash_hal_mock.h"
+
+#define DEFAULT_LITTLEFS_FILE_NAME "littlefs.bin"
+
+class LittleFSMock {
+public:
+    LittleFSMock(ssize_t fs_size, size_t fs_block, size_t fs_page, const String& storage = emptyString);
+    void reset();
+    ~LittleFSMock();
+    
+protected:
+    void load ();
+    void save ();
+
+    std::vector<uint8_t> m_fs;
+    String m_storage;
+    bool m_overwrite;
+};
+
+#define LITTLEFS_MOCK_DECLARE(size_kb, block_kb, page_b, storage) LittleFSMock littlefs_mock(size_kb * 1024, block_kb * 1024, page_b, storage)
+#define LITTLEFS_MOCK_RESET() littlefs_mock.reset()
+
+#endif /* littlefs_mock_hpp */
diff --git a/tests/host/common/mock.h b/tests/host/common/mock.h
index e681d64975..3824b62a4b 100644
--- a/tests/host/common/mock.h
+++ b/tests/host/common/mock.h
@@ -135,6 +135,8 @@ void register_udp (int sock, UdpContext* udp = nullptr);
 
 void mock_start_spiffs (const String& fname, size_t size_kb, size_t block_kb = 8, size_t page_b = 512);
 void mock_stop_spiffs ();
+void mock_start_littlefs (const String& fname, size_t size_kb, size_t block_kb = 8, size_t page_b = 512);
+void mock_stop_littlefs ();
 
 //
 
diff --git a/tests/host/common/sdfs_mock.cpp b/tests/host/common/sdfs_mock.cpp
index 2a0433756b..ef2ae44152 100644
--- a/tests/host/common/sdfs_mock.cpp
+++ b/tests/host/common/sdfs_mock.cpp
@@ -17,5 +17,5 @@
 #include "../../../libraries/SDFS/src/SDFS.h"
 
 #define SDSIZE 16LL
-uint64_t _sdCardSizeB = SDSIZE * 1024LL * 1024LL;
-uint8_t _sdCard[SDSIZE * 1024LL * 1024LL];
+uint64_t _sdCardSizeB = 0;
+uint8_t *_sdCard = nullptr;
diff --git a/tests/host/common/sdfs_mock.h b/tests/host/common/sdfs_mock.h
index 5ee07f8ab9..4508e1a903 100644
--- a/tests/host/common/sdfs_mock.h
+++ b/tests/host/common/sdfs_mock.h
@@ -23,11 +23,21 @@
 
 class SDFSMock {
 public:
-    SDFSMock() { }
+    SDFSMock(ssize_t fs_size, size_t fs_block, size_t fs_page, const String& storage = emptyString) { }
     void reset() { }
     ~SDFSMock() { }
 };
 
-#define SDFS_MOCK_DECLARE() SDFSMock sdfs_mock();
+extern uint64_t _sdCardSizeB;
+extern uint8_t *_sdCard;
+
+#define SDFS_MOCK_DECLARE(size_kb, block_kb, page_b, storage) \
+    SDFS.end(); \
+    SDFSMock sdfs_mock(size_kb * 1024, block_kb * 1024, page_b, storage); free(_sdCard); \
+    _sdCardSizeB = size_kb ? 16 * 1024 * 1024 : 0; \
+    if (_sdCardSizeB) _sdCard = (uint8_t*)calloc(_sdCardSizeB, 1); \
+    else _sdCard = nullptr; \
+    SDFS.setConfig(SDFSConfig().setAutoFormat(true));
+#define SDFS_MOCK_RESET() sdfs_mock.reset()
 
 #endif /* spiffs_mock_hpp */
diff --git a/tests/host/common/spiffs_mock.cpp b/tests/host/common/spiffs_mock.cpp
index 004b15fb9a..3e85fa06ab 100644
--- a/tests/host/common/spiffs_mock.cpp
+++ b/tests/host/common/spiffs_mock.cpp
@@ -27,14 +27,9 @@
 #include <fcntl.h>
 #include <unistd.h>
 
-extern "C"
-{
-    static uint32_t s_phys_addr = 0;
-    uint32_t s_phys_size = 0;
-    uint32_t s_phys_page = 0;
-    uint32_t s_phys_block = 0;
-    uint8_t* s_phys_data = nullptr;
-}
+#include "flash_hal_mock.h"
+
+#define SPIFFS_FILE_NAME "spiffs.bin"
 
 FS SPIFFS(nullptr);
 
@@ -57,7 +52,7 @@ SpiffsMock::SpiffsMock(ssize_t fs_size, size_t fs_block, size_t fs_page, const S
 
 void SpiffsMock::reset()
 {
-    SPIFFS = FS(FSImplPtr(new SPIFFSImpl(0, s_phys_size, s_phys_page, s_phys_block, 5)));
+    SPIFFS = FS(FSImplPtr(new spiffs_impl::SPIFFSImpl(0, s_phys_size, s_phys_page, s_phys_block, 5)));
     load();
 }
 
@@ -131,26 +126,3 @@ void SpiffsMock::save ()
     if (::close(fs) == -1)
         fprintf(stderr, "SPIFFS: closing %s: %s\n", m_storage.c_str(), strerror(errno));
 }
-
-int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
-    memcpy(dst, s_phys_data + addr, size);
-    return SPIFFS_OK;
-}
-
-int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) {
-    memcpy(s_phys_data + addr, src, size);
-    return SPIFFS_OK;
-}
-
-int32_t spiffs_hal_erase(uint32_t addr, uint32_t size) {
-    if ((size & (FLASH_SECTOR_SIZE - 1)) != 0 ||
-        (addr & (FLASH_SECTOR_SIZE - 1)) != 0) {
-        abort();
-    }
-    const uint32_t sector = addr / FLASH_SECTOR_SIZE;
-    const uint32_t sectorCount = size / FLASH_SECTOR_SIZE;
-    for (uint32_t i = 0; i < sectorCount; ++i) {
-        memset(s_phys_data + (sector + i) * FLASH_SECTOR_SIZE, 0xff, FLASH_SECTOR_SIZE);
-    }
-    return SPIFFS_OK;
-}
diff --git a/tests/host/common/spiffs_mock.h b/tests/host/common/spiffs_mock.h
index a38fd820e3..4c265964f5 100644
--- a/tests/host/common/spiffs_mock.h
+++ b/tests/host/common/spiffs_mock.h
@@ -20,6 +20,7 @@
 #include <stddef.h>
 #include <vector>
 #include <FS.h>
+#include "flash_hal_mock.h"
 
 #define DEFAULT_SPIFFS_FILE_NAME "spiffs.bin"
 
diff --git a/tests/host/fs/test_fs.cpp b/tests/host/fs/test_fs.cpp
index 9cd4fcc1ea..28dd0bab5b 100644
--- a/tests/host/fs/test_fs.cpp
+++ b/tests/host/fs/test_fs.cpp
@@ -17,39 +17,29 @@
 #include <map>
 #include <FS.h>
 #include "../common/spiffs_mock.h"
+#include "../common/littlefs_mock.h"
 #include "../common/sdfs_mock.h"
 #include <spiffs/spiffs.h>
+#include <LittleFS.h>
 #include "../../../libraries/SDFS/src/SDFS.h"
 #include "../../../libraries/SD/src/SD.h"
 
-static void createFile (const char* name, const char* content)
-{
-    auto f = SPIFFS.open(name, "w");
-    REQUIRE(f);
-    if (content) {
-        f.print(content);
-    }
-}
-
-static String readFile (const char* name)
-{
-    auto f = SPIFFS.open(name, "r");
-    if (f) {
-        return f.readString();
-    }
-    return String();
-}
 
-static std::set<String> listDir (const char* path)
-{
-    std::set<String> result;
-    Dir dir = SPIFFS.openDir(path);
-    while (dir.next()) {
-        REQUIRE(result.find(dir.fileName()) == std::end(result));
-        result.insert(dir.fileName());
-    }
-    return result;
-}
+namespace spiffs_test {
+#define FSTYPE SPIFFS
+#define TESTPRE "SPIFFS - "
+#define TESTPAT "[fs]"
+#define TOOLONGFILENAME "/2345678901234567890123456789012"
+#define FS_MOCK_DECLARE SPIFFS_MOCK_DECLARE
+#define FS_MOCK_RESET SPIFFS_MOCK_RESET
+#undef FS_HAS_DIRS
+#include "test_fs.inc"
+#undef FSTYPE
+#undef TESTPRE
+#undef TESTPAT
+#undef TOOLONGFILENAME
+#undef FS_MOCK_DECLARE
+#undef FS_MOCK_RESET
 
 TEST_CASE("SPIFFS checks the config object passed in", "[fs]")
 {
@@ -57,286 +47,90 @@ TEST_CASE("SPIFFS checks the config object passed in", "[fs]")
     FSConfig f;
     SPIFFSConfig s;
     SDFSConfig d;
+    LittleFSConfig l;
 
     REQUIRE_FALSE(SPIFFS.setConfig(f));
     REQUIRE(SPIFFS.setConfig(s));
     REQUIRE_FALSE(SPIFFS.setConfig(d));
+    REQUIRE_FALSE(LittleFS.setConfig(l));
 }
 
+};
+
+
+namespace littlefs_test {
+#define FSTYPE LittleFS
+#define TESTPRE "LittleFS - "
+#define TESTPAT "[lfs]"
+// LittleFS routines strip leading slashes before doing anything, so up to 31 char names are allowable
+#define TOOLONGFILENAME "/12345678901234567890123456789012"
+#define FS_MOCK_DECLARE LITTLEFS_MOCK_DECLARE
+#define FS_MOCK_RESET LITTLEFS_MOCK_RESET
+#define FS_HAS_DIRS
+#include "test_fs.inc"
+#undef FSTYPE
+#undef TESTPRE
+#undef TESTPAT
+#undef TOOLONGFILENAME
+#undef FS_MOCK_DECLARE
+#undef FS_MOCK_RESET
+
+TEST_CASE("LittleFS checks the config object passed in", "[fs]")
+{
+    LITTLEFS_MOCK_DECLARE(64, 8, 512, "");
+    FSConfig f;
+    SPIFFSConfig s;
+    SDFSConfig d;
+    LittleFSConfig l;
+
+    REQUIRE_FALSE(LittleFS.setConfig(f));
+    REQUIRE_FALSE(LittleFS.setConfig(s));
+    REQUIRE_FALSE(LittleFS.setConfig(d));
+    REQUIRE(LittleFS.setConfig(l));
+}
+
+};
+
+namespace sdfs_test {
+#define FSTYPE SDFS
+#define TESTPRE "SDFS - "
+#define TESTPAT "[sdfs]"
+// SDFS supports long paths (MAXPATH)
+#define TOOLONGFILENAME "/" \
+	"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" \
+	"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" \
+	"12345678901234567890123456789012345678901234567890123456"
+#define FS_MOCK_DECLARE SDFS_MOCK_DECLARE
+#define FS_MOCK_RESET SDFS_MOCK_RESET
+#define FS_HAS_DIRS
+#include "test_fs.inc"
+#undef FSTYPE
+#undef TESTPRE
+#undef TESTPAT
+#undef TOOLONGFILENAME
+#undef FS_MOCK_DECLARE
+#undef FS_MOCK_RESET
+
 TEST_CASE("SDFS checks the config object passed in", "[fs]")
 {
-    SDFS_MOCK_DECLARE();
+    SDFS_MOCK_DECLARE(64, 8, 512, "");
     FSConfig f;
     SPIFFSConfig s;
     SDFSConfig d;
+    LittleFSConfig l;
 
     REQUIRE_FALSE(SDFS.setConfig(f));
     REQUIRE_FALSE(SDFS.setConfig(s));
     REQUIRE(SDFS.setConfig(d));
+    REQUIRE_FALSE(SDFS.setConfig(l));
 }
 
-TEST_CASE("FS can begin","[fs]")
-{
-    SPIFFS_MOCK_DECLARE(64, 8, 512, "");
-    SPIFFSConfig cfg;
-    cfg.setAutoFormat(false);
-    SPIFFS.setConfig(cfg);
-    REQUIRE_FALSE(SPIFFS.begin());
-    cfg.setAutoFormat(true);
-    SPIFFS.setConfig(cfg);
-    REQUIRE(SPIFFS.begin());
-    REQUIRE_FALSE(SPIFFS.setConfig(cfg)); // Can't change config of mounted FS
-}
+// Also a SD specific test to check that FILE_OPEN is really an append operation:
 
-TEST_CASE("FS can't begin with zero size","[fs]")
+TEST_CASE("SD.h FILE_WRITE macro is append", "[fs]")
 {
-    SPIFFS_MOCK_DECLARE(0, 8, 512, "");
-    REQUIRE_FALSE(SPIFFS.begin());
-}
-
-TEST_CASE("Before begin is called, open will fail","[fs]")
-{
-    SPIFFS_MOCK_DECLARE(64, 8, 512, "");
-    REQUIRE_FALSE(SPIFFS.open("/foo", "w"));
-}
-
-TEST_CASE("FS can create file","[fs]")
-{
-    SPIFFS_MOCK_DECLARE(64, 8, 512, "");
-    REQUIRE(SPIFFS.begin());
-    createFile("/test", "");
-    REQUIRE(SPIFFS.exists("/test"));
-}
-
-TEST_CASE("Files can be written and appended to","[fs]")
-{
-    SPIFFS_MOCK_DECLARE(64, 8, 512, "");
-    REQUIRE(SPIFFS.begin());
-    {
-        File f = SPIFFS.open("config1.txt", "w");
-        REQUIRE(f);
-        f.println("file 1");
-    }
-    {
-        File f = SPIFFS.open("config1.txt", "a");
-        REQUIRE(f);
-        f.println("file 1 again");
-    }
-    {
-        File f = SPIFFS.open("config1.txt", "r");
-        REQUIRE(f);
-        char buf[128];
-        size_t len = f.read((uint8_t*)buf, sizeof(buf));
-        buf[len] = 0;
-        REQUIRE(strcmp(buf, "file 1\r\nfile 1 again\r\n") == 0);
-    }
-}
-
-TEST_CASE("Files persist after reset", "[fs]")
-{
-    SPIFFS_MOCK_DECLARE(64, 8, 512, "");
-    REQUIRE(SPIFFS.begin());
-    createFile("config1.txt", "file 1");
-
-    SPIFFS_MOCK_RESET();
-    REQUIRE(SPIFFS.begin());
-    REQUIRE(readFile("config1.txt") == "file 1");
-}
-
-
-TEST_CASE("Filesystem is empty after format", "[fs]")
-{
-    SPIFFS_MOCK_DECLARE(64, 8, 512, "");
-    REQUIRE(SPIFFS.format());
-    REQUIRE(SPIFFS.begin());
-    createFile("/1", "first");
-    createFile("/2", "second");
-    REQUIRE(SPIFFS.format());
-    Dir root = SPIFFS.openDir("/");
-    size_t count = 0;
-    while (root.next()) {
-        ++count;
-    }
-    REQUIRE(count == 0);
-}
-
-TEST_CASE("Dir lists all files", "[fs]")
-{
-    SPIFFS_MOCK_DECLARE(64, 8, 512, "");
-    REQUIRE(SPIFFS.begin());
-    createFile("/empty", "");
-    createFile("/not_empty", "some text");
-    createFile("/another", "more text");
-    createFile("/subdir/empty", "");
-    createFile("/subdir/not_empty", "text again");
-    auto files = listDir("/");
-    REQUIRE(files.size() == 5);
-    REQUIRE(files.find("/empty") != std::end(files));
-    REQUIRE(files.find("/not_empty") != std::end(files));
-    REQUIRE(files.find("/another") != std::end(files));
-    REQUIRE(files.find("/subdir/empty") != std::end(files));
-    REQUIRE(files.find("/subdir/not_empty") != std::end(files));
-}
-
-TEST_CASE("File names which are too long are rejected", "[fs]")
-{
-    SPIFFS_MOCK_DECLARE(64, 8, 512, "");
-    REQUIRE(SPIFFS.begin());
-    const char* emptyName = "";
-    const char* longName_31 = "/234567890123456789012345678901";
-    const char* longName_32 = "/2345678901234567890123456789012";
-    REQUIRE_FALSE(SPIFFS.open(emptyName, "w"));
-    REQUIRE_FALSE(SPIFFS.open(emptyName, "r"));
-    REQUIRE_FALSE(SPIFFS.exists(emptyName));
-    REQUIRE_FALSE(SPIFFS.open(longName_32, "w"));
-    REQUIRE_FALSE(SPIFFS.open(longName_32, "r"));
-    REQUIRE_FALSE(SPIFFS.exists(longName_32));
-    REQUIRE(SPIFFS.open(longName_31, "w"));
-    REQUIRE(SPIFFS.open(longName_31, "r"));
-    REQUIRE(SPIFFS.exists(longName_31));
-}
-
-TEST_CASE("#1685 Duplicate files", "[fs][bugreport]")
-{
-    SPIFFS_MOCK_DECLARE(64, 8, 512, "");
-    REQUIRE(SPIFFS.begin());
-    createFile("/config", "some text");
-    createFile("/data", "");
-    readFile("/config");
-    createFile("/data", "more text");
-    listDir("/");
-}
-
-TEST_CASE("#1819 Can list all files with openDir(\"\")", "[fs][bugreport]")
-{
-    SPIFFS_MOCK_DECLARE(64, 8, 512, "");
-    REQUIRE(SPIFFS.begin());
-    createFile("/file1", "some text");
-    createFile("/file2", "other text");
-    createFile("file3", "more text");
-    createFile("sorta-dir/file4", "\n");
-    auto files = listDir("");
-    REQUIRE(files.size() == 4);
-}
-
-TEST_CASE("truncate", "[fs][spiffs]")
-{
-    SPIFFS_MOCK_DECLARE(64, 8, 512, "");
-    REQUIRE(SPIFFS.begin());
-    createFile("/file1", "some text");
-    auto f = SPIFFS.open("/file1", "r");
-    f.truncate(4);
-    f.close();
-    String s = readFile("/file1");
-    REQUIRE( s == "some" );
-}
-
-TEST_CASE("SDFS", "[sdfs]")
-{
-    SDFS_MOCK_DECLARE();
-    auto cfg = SDFSConfig(0, SD_SCK_MHZ(1));
-    SDFS.setConfig(cfg);
-    REQUIRE(SDFS.format());
+    SDFS_MOCK_DECLARE(64, 8, 512, "");
     REQUIRE(SDFS.begin());
-    REQUIRE_FALSE(SDFS.setConfig(cfg)); // Can't change config of mounted fs
-    REQUIRE(SDFS.mkdir("/happy/face"));
-    REQUIRE(SDFS.mkdir("/happy/nose"));
-    REQUIRE(SDFS.rmdir("/happy/face"));
-    auto f = SDFS.open("/this/is/a/long/name.txt", "w");
-    f.printf("Hello\n");
-    f.close();
-    SDFS.end();
-}
-
-TEST_CASE("Files.ino example", "[sd]")
-{
-    SDFS_MOCK_DECLARE();
-    SDFS.end();
-    SDFS.setConfig(SDFSConfig(0, SD_SCK_MHZ(1)));
-    REQUIRE(SDFS.format());
-    REQUIRE(SD.begin(4));
-    REQUIRE_FALSE(SD.exists("example.txt"));
-    File myFile = SD.open("example.txt", FILE_WRITE);
-    REQUIRE(myFile);
-    myFile.close();
-    REQUIRE(SD.exists("example.txt"));
-    REQUIRE(SD.remove("example.txt"));
-    REQUIRE_FALSE(SD.exists("example.txt"));
-    SDFS.end();
-}
-
-
-static void createFileSD(const char* name, const char* content)
-{
-    auto f = SD.open(name, FILE_WRITE);
-    REQUIRE(f);
-    if (content) {
-        f.print(content);
-    }
-}
-
-static String readFileSD(const char* name)
-{
-    auto f = SD.open(name, FILE_READ);
-    if (f) {
-        return f.readString();
-    }
-    return String();
-}
-
-TEST_CASE("Listfiles.ino example", "[sd]")
-{
-    SDFS_MOCK_DECLARE();
-    SDFS.setConfig(SDFSConfig(0, SD_SCK_MHZ(1)));
-    REQUIRE(SDFS.format());
-    REQUIRE(SD.begin(4));
-
-    createFileSD("file1", "hello");
-    createFileSD("file2", "hola");
-    createFileSD("dir1/file3", "nihao");
-    createFileSD("dir2/dir3/file4", "bonjour");
-
-    File root = SD.open("/");
-    File file1 = root.openNextFile();
-    File file2 = root.openNextFile();
-    File dir1 = root.openNextFile();
-    File dir1_file3 = dir1.openNextFile();
-    File dir2 = root.openNextFile();
-    File dir2_dir3 = dir2.openNextFile();
-    File dir2_dir3_file4 = dir2_dir3.openNextFile();
-
-    bool ok;
-    ok = root.isDirectory() && !root.isFile() && !strcmp(root.name(), "/");
-    REQUIRE(ok);
-    ok = !file1.isDirectory() && file1.isFile() && !strcmp(file1.name(), "file1");
-    REQUIRE(ok);
-    ok = !file2.isDirectory() && file2.isFile() && !strcmp(file2.name(), "file2");
-    REQUIRE(ok);
-    ok = dir1.isDirectory() && !dir1.isFile() && !strcmp(dir1.name(), "dir1");
-    REQUIRE(ok);
-    ok = !dir1_file3.isDirectory() && dir1_file3.isFile() && !strcmp(dir1_file3.name(), "file3") &&
-         !strcmp(dir1_file3.fullName(), "dir1/file3");
-    REQUIRE(ok);
-    ok = dir2.isDirectory() && !dir2.isFile() && !strcmp(dir2.name(), "dir2");
-    REQUIRE(ok);
-    ok = dir2_dir3.isDirectory() && !dir2_dir3.isFile() && !strcmp(dir2_dir3.name(), "dir3");
-    REQUIRE(ok);
-    ok = !dir2_dir3_file4.isDirectory() && dir2_dir3_file4.isFile() && !strcmp(dir2_dir3_file4.name(), "file4") &&
-         !strcmp(dir2_dir3_file4.fullName(), "dir2/dir3/file4");
-    REQUIRE(ok);
-
-    REQUIRE(readFileSD("/file1") == "hello");
-    REQUIRE(readFileSD("file2") == "hola");
-    REQUIRE(readFileSD("dir1/file3") == "nihao");
-    REQUIRE(readFileSD("/dir2/dir3/file4") == "bonjour");
-}
-
-TEST_CASE("Multisplendored File::writes", "[fs]")
-{
-    SDFS_MOCK_DECLARE();
-    SDFS.end();
-    SDFS.setConfig(SDFSConfig(0, SD_SCK_MHZ(1)));
-    REQUIRE(SDFS.format());
     REQUIRE(SD.begin(4));
 
     File f = SD.open("/file.txt", FILE_WRITE);
@@ -349,7 +143,11 @@ TEST_CASE("Multisplendored File::writes", "[fs]")
     uint32_t bigone = 0x40404040;
     f.write((const uint8_t*)&bigone, 4);
     f.close();
-    REQUIRE(readFileSD("/file.txt") == "aAbbcctheendxyz@@@@");
+    REQUIRE(readFile("/file.txt") == "aAbbcctheendxyz@@@@");
+    f = SD.open("/file.txt", FILE_WRITE);
+    f.write("append", 6);
+    f.close();
+    REQUIRE(readFile("/file.txt") == "aAbbcctheendxyz@@@@append");
     File g = SD.open("/file2.txt", FILE_WRITE);
     g.write(0);
     g.close();
@@ -359,3 +157,5 @@ TEST_CASE("Multisplendored File::writes", "[fs]")
     g.close();
     REQUIRE(u == 0);
 }
+
+};
diff --git a/tests/host/fs/test_fs.inc b/tests/host/fs/test_fs.inc
new file mode 100644
index 0000000000..e6bedfa181
--- /dev/null
+++ b/tests/host/fs/test_fs.inc
@@ -0,0 +1,346 @@
+static void createFile (const char* name, const char* content)
+{
+    auto f = FSTYPE.open(name, "w");
+    REQUIRE(f);
+    if (content) {
+        f.print(content);
+    }
+}
+
+static String readFile (const char* name)
+{
+    auto f = FSTYPE.open(name, "r");
+    if (f) {
+        return f.readString();
+    }
+    return String();
+}
+
+static std::set<String> listDir (const char* path)
+{
+    std::set<String> result;
+    Dir dir = FSTYPE.openDir(path);
+    while (dir.next()) {
+        REQUIRE(result.find(dir.fileName()) == std::end(result));
+        result.insert(dir.fileName());
+    }
+    return result;
+}
+
+TEST_CASE(TESTPRE "FS can begin",TESTPAT)
+{
+    FS_MOCK_DECLARE(64, 8, 512, "");
+    REQUIRE(FSTYPE.begin());
+}
+
+TEST_CASE(TESTPRE "FS can't begin with zero size",TESTPAT)
+{
+    FS_MOCK_DECLARE(0, 8, 512, "");
+    REQUIRE_FALSE(FSTYPE.begin());
+}
+
+TEST_CASE(TESTPRE "Before begin is called, open will fail",TESTPAT)
+{
+    FS_MOCK_DECLARE(64, 8, 512, "");
+    REQUIRE_FALSE(FSTYPE.open("/foo", "w"));
+}
+
+TEST_CASE(TESTPRE "FS can create file",TESTPAT)
+{
+    FS_MOCK_DECLARE(64, 8, 512, "");
+    REQUIRE(FSTYPE.begin());
+    createFile("/test", "");
+    REQUIRE(FSTYPE.exists("/test"));
+}
+
+TEST_CASE(TESTPRE "Files can be written and appended to",TESTPAT)
+{
+    FS_MOCK_DECLARE(64, 8, 512, "");
+    REQUIRE(FSTYPE.begin());
+    {
+        File f = FSTYPE.open("config1.txt", "w");
+        REQUIRE(f);
+        f.println("file 1");
+    }
+    {
+        File f = FSTYPE.open("config1.txt", "a");
+        REQUIRE(f);
+        f.println("file 1 again");
+    }
+    {
+        File f = FSTYPE.open("config1.txt", "r");
+        REQUIRE(f);
+        char buf[128];
+        size_t len = f.read((uint8_t*)buf, sizeof(buf));
+        buf[len] = 0;
+        REQUIRE(strcmp(buf, "file 1\r\nfile 1 again\r\n") == 0);
+    }
+}
+
+TEST_CASE(TESTPRE "Files persist after reset", TESTPAT)
+{
+    FS_MOCK_DECLARE(64, 8, 512, "");
+    REQUIRE(FSTYPE.begin());
+    createFile("config1.txt", "file 1");
+
+    FS_MOCK_RESET();
+    REQUIRE(FSTYPE.begin());
+    REQUIRE(readFile("config1.txt") == "file 1");
+}
+
+
+TEST_CASE(TESTPRE "Filesystem is empty after format", TESTPAT)
+{
+    FS_MOCK_DECLARE(64, 8, 512, "");
+    REQUIRE(FSTYPE.format());
+    REQUIRE(FSTYPE.begin());
+    createFile("/1", "first");
+    createFile("/2", "second");
+    FSTYPE.end();
+    REQUIRE(FSTYPE.format());
+    REQUIRE(FSTYPE.begin());
+    Dir root = FSTYPE.openDir("/");
+    size_t count = 0;
+    while (root.next()) {
+        ++count;
+    }
+    REQUIRE(count == 0);
+}
+
+TEST_CASE(TESTPRE "File names which are too long are rejected", TESTPAT)
+{
+    FS_MOCK_DECLARE(64, 8, 512, "");
+    REQUIRE(FSTYPE.begin());
+    const char* emptyName = "";
+    const char* longName_31 = "/234567890123456789012345678901";
+    const char* longName_32 = TOOLONGFILENAME;
+    REQUIRE_FALSE(FSTYPE.open(emptyName, "w"));
+    REQUIRE_FALSE(FSTYPE.open(emptyName, "r"));
+    REQUIRE_FALSE(FSTYPE.exists(emptyName));
+    REQUIRE_FALSE(FSTYPE.open(longName_32, "w"));
+    REQUIRE_FALSE(FSTYPE.open(longName_32, "r"));
+    REQUIRE_FALSE(FSTYPE.exists(longName_32));
+    REQUIRE(FSTYPE.open(longName_31, "w"));
+    REQUIRE(FSTYPE.open(longName_31, "r"));
+    REQUIRE(FSTYPE.exists(longName_31));
+}
+
+TEST_CASE(TESTPRE "#1685 Duplicate files", "[fs][bugreport]")
+{
+    FS_MOCK_DECLARE(64, 8, 512, "");
+    REQUIRE(FSTYPE.begin());
+    createFile("/config", "some text");
+    createFile("/data", "");
+    readFile("/config");
+    createFile("/data", "more text");
+    auto files = listDir("/");
+    REQUIRE(files.size() == 2);
+}
+
+TEST_CASE(TESTPRE "#1819 Can list all files with openDir(\"\")", "[fs][bugreport]")
+{
+    FS_MOCK_DECLARE(96, 8, 512, "");
+    REQUIRE(FSTYPE.begin());
+    createFile("/file1", "some text");
+    createFile("/file2", "other text");
+    createFile("file3", "more text");
+    createFile("sorta-dir/file4", "\n");
+    auto files = listDir("");
+    REQUIRE(files.size() == 4);
+}
+
+TEST_CASE(TESTPRE "truncate", TESTPAT)
+{
+    FS_MOCK_DECLARE(64, 8, 512, "");
+    REQUIRE(FSTYPE.begin());
+    createFile("/file1", "some text");
+    auto f = FSTYPE.open("/file1", "r+");
+    REQUIRE(f.truncate(4));
+    f.close();
+    String s = readFile("/file1");
+    REQUIRE( s == "some" );
+}
+
+#ifdef FS_HAS_DIRS
+
+#if FSTYPE != SDFS
+// We silently make subdirectories if they do not exist and silently remove
+// them when they're no longer needed, so make sure we can clean up after
+// ourselves.  At some point we may drop this and go to normal POSIX mkdir
+// behavior and expose the FS::mkdir() method, but for now this works OK.
+TEST_CASE(TESTPRE "Removing all files in a subdir removes that subdir", TESTPAT)
+{
+    FS_MOCK_DECLARE(128, 8, 512, "");
+    REQUIRE(FSTYPE.begin());
+    createFile("/empty", "");
+    createFile("/not_empty", "some text");
+    createFile("/another", "more text");
+    createFile("/subdir/empty", "");
+    createFile("/subdir/not_empty", "text again");
+    auto files = listDir("/");
+    REQUIRE(files.size() == 4);
+    files = listDir("/subdir");
+    REQUIRE(files.size() == 2);
+    // Delete one of subdir, should still exist afterwards
+    FSTYPE.remove("subdir/empty");
+    files = listDir("/subdir");
+    REQUIRE(files.size() == 1);
+    FSTYPE.remove("subdir/not_empty");
+    files = listDir("/subdir");
+    REQUIRE(files.size() == 0);
+    files = listDir("/");
+    REQUIRE(files.size() == 3);
+    REQUIRE(files.find("subdir") == std::end(files));
+}
+#endif
+
+// LittleFS openDir is slightly different than SPIFFS.  In SPIFFS there
+// are no directories and "/" is just another character, so "/a/b/c" is a
+// file in the root dir whose name is "/a/b/c".  In LittleFS we have full
+// directory support, so "/a/b/c" is a file "c" in the "/a/b" dir.
+// This means that if you iterate over dirOpen("/") on SPIFFS you get
+// a list of every file, including "subdirs".  On LittleFS, you need to
+// explicitly open the subdir to see its files.  This behavior is the
+// same as POSIX readdir(), and helps isolate subdirs from each other.
+// Also note that the returned filenames in the "dir.next()" operator
+// will be in that subdir (i.e. if you opendir("/a/b"); f=dir.next();"
+// f.name == "c" and not "/a/b/c" as you would see in SPIFFS.
+TEST_CASE(TESTPRE "Dir lists all files", TESTPAT)
+{
+    FS_MOCK_DECLARE(64, 8, 512, "");
+    REQUIRE(FSTYPE.begin());
+    createFile("/empty", "");
+    createFile("/not_empty", "some text");
+    createFile("/another", "more text");
+    createFile("/subdir/empty", "");
+    createFile("/subdir/not_empty", "text again");
+    auto files = listDir("/");
+    REQUIRE(files.size() == 4);
+    bool empty = (files.find("/empty") != std::end(files)) || (files.find("empty") != std::end(files));
+    REQUIRE(empty);
+    bool not_empty = (files.find("/not_empty") != std::end(files)) || (files.find("not_empty") != std::end(files));
+    REQUIRE(not_empty);
+    bool another = (files.find("/another") != std::end(files)) ||  (files.find("another") != std::end(files));
+    REQUIRE(another);
+
+    files = listDir("/subdir");
+    REQUIRE(files.size() == 2);
+    bool sub_empty = (files.find("/empty") != std::end(files)) || (files.find("empty") != std::end(files));
+    REQUIRE(sub_empty);
+    bool sub_not_empty = (files.find("/not_empty") != std::end(files)) || (files.find("not_empty") != std::end(files));
+    REQUIRE(sub_not_empty);
+}
+
+File FindFileByName(const File f[], int count, const char *name)
+{
+    for (int i=0; i<count; i++) {
+      if (!strcmp(name, f[i].name())) return f[i];
+    }
+    return f[0];
+}
+
+TEST_CASE(TESTPRE "Listfiles.ino example", TESTPAT)
+{
+    FS_MOCK_DECLARE(128, 8, 512, "");
+    REQUIRE(FSTYPE.format());
+    REQUIRE(FSTYPE.begin());
+
+    createFile("file1", "hello");
+    createFile("file2", "hola");
+    createFile("dir1/file3", "nihao");
+    createFile("dir2/dir3/file4", "bonjour");
+
+    File root = FSTYPE.open("/", "r");
+    // LittleFS and SDFS are not guaranteed to put the names in order of creation, so
+    // manually look for them...
+    File f[4];
+    f[0] = root.openNextFile();
+    f[1] = root.openNextFile();
+    f[2] = root.openNextFile();
+    f[3] = root.openNextFile();
+    File file1 = FindFileByName(f, 4, "file1");
+    File file2 = FindFileByName(f, 4, "file2");
+    File dir1 = FindFileByName(f, 4, "dir1");
+    File dir1_file3 = dir1.openNextFile();
+    File dir2 = FindFileByName(f, 4, "dir2");
+    File dir2_dir3 = dir2.openNextFile();
+    File dir2_dir3_file4 = dir2_dir3.openNextFile();
+
+    bool ok;
+    ok = root.isDirectory() && !root.isFile() && !strcmp(root.name(), "/");
+    REQUIRE(ok);
+    ok = !file1.isDirectory() && file1.isFile() && !strcmp(file1.name(), "file1");
+    REQUIRE(ok);
+    ok = !file2.isDirectory() && file2.isFile() && !strcmp(file2.name(), "file2");
+    REQUIRE(ok);
+    ok = dir1.isDirectory() && !dir1.isFile() && !strcmp(dir1.name(), "dir1");
+    REQUIRE(ok);
+    ok = !dir1_file3.isDirectory() && dir1_file3.isFile() && !strcmp(dir1_file3.name(), "file3") &&
+         !strcmp(dir1_file3.fullName(), "dir1/file3");
+    REQUIRE(ok);
+    ok = dir2.isDirectory() && !dir2.isFile() && !strcmp(dir2.name(), "dir2");
+    REQUIRE(ok);
+    ok = dir2_dir3.isDirectory() && !dir2_dir3.isFile() && !strcmp(dir2_dir3.name(), "dir3");
+    REQUIRE(ok);
+    ok = !dir2_dir3_file4.isDirectory() && dir2_dir3_file4.isFile() && !strcmp(dir2_dir3_file4.name(), "file4") &&
+         !strcmp(dir2_dir3_file4.fullName(), "dir2/dir3/file4");
+    REQUIRE(ok);
+
+    REQUIRE(readFile("/file1") == "hello");
+    REQUIRE(readFile("file2") == "hola");
+    REQUIRE(readFile("dir1/file3") == "nihao");
+    REQUIRE(readFile("/dir2/dir3/file4") == "bonjour");
+}
+
+#else // !FS_HAS_DIRS
+
+TEST_CASE(TESTPRE "Dir lists all files", TESTPAT)
+{
+    FS_MOCK_DECLARE(64, 8, 512, "");
+    REQUIRE(FSTYPE.begin());
+    createFile("/empty", "");
+    createFile("/not_empty", "some text");
+    createFile("/another", "more text");
+    createFile("/subdir/empty", "");
+    createFile("/subdir/not_empty", "text again");
+    auto files = listDir("/");
+    REQUIRE(files.size() == 5);
+    bool empty = (files.find("/empty") != std::end(files)) || (files.find("empty") != std::end(files));
+    REQUIRE(empty);
+    bool not_empty = (files.find("/not_empty") != std::end(files)) || (files.find("not_empty") != std::end(files));
+    REQUIRE(not_empty);
+    bool another = (files.find("/another") != std::end(files)) ||  (files.find("another") != std::end(files));
+    REQUIRE(another);
+    bool sub_empty = (files.find("/subdir/empty") != std::end(files)) || (files.find("subdir/empty") != std::end(files));
+    REQUIRE(sub_empty);
+    bool sub_not_empty = (files.find("/subdir/not_empty") != std::end(files)) || (files.find("subdir/not_empty") != std::end(files));
+    REQUIRE(sub_not_empty);
+}
+
+TEST_CASE(TESTPRE "Multisplendored File::writes", TESTPAT)
+{
+    FS_MOCK_DECLARE(64, 8, 512, "");
+    REQUIRE(FSTYPE.begin());
+
+    File f = FSTYPE.open("/file.txt", "w");
+    f.write('a');
+    f.write(65);
+    f.write("bbcc");
+    f.write("theend", 6);
+    char block[3]={'x','y','z'};
+    f.write(block, 3);
+    uint32_t bigone = 0x40404040;
+    f.write((const uint8_t*)&bigone, 4);
+    f.close();
+    REQUIRE(readFile("/file.txt") == "aAbbcctheendxyz@@@@");
+    File g = FSTYPE.open("/file.txt", "w");
+    g.write(0);
+    g.close();
+    g = FSTYPE.open("/file.txt", "r");
+    uint8_t u = 0x66;
+    g.read(&u, 1);
+    g.close();
+    REQUIRE(u == 0);
+}
+
+#endif
diff --git a/tools/boards.txt.py b/tools/boards.txt.py
index 3338330f27..c8c51b0407 100755
--- a/tools/boards.txt.py
+++ b/tools/boards.txt.py
@@ -1252,10 +1252,10 @@ def flash_map (flashsize_kb, spiffs_kb = 0):
         print("  irom0_0_seg :                         org = 0x40201010, len = 0x%x" % max_upload_size)
         print("}")
         print("")
-        print("PROVIDE ( _SPIFFS_start = 0x%08X );" % (0x40200000 + spiffs_start))
-        print("PROVIDE ( _SPIFFS_end = 0x%08X );" % (0x40200000 + spiffs_end))
-        print("PROVIDE ( _SPIFFS_page = 0x%X );" % page)
-        print("PROVIDE ( _SPIFFS_block = 0x%X );" % block)
+        print("PROVIDE ( _FS_start = 0x%08X );" % (0x40200000 + spiffs_start))
+        print("PROVIDE ( _FS_end = 0x%08X );" % (0x40200000 + spiffs_end))
+        print("PROVIDE ( _FS_page = 0x%X );" % page)
+        print("PROVIDE ( _FS_block = 0x%X );" % block)
         print("")
         print('INCLUDE "local.eagle.app.v6.common.ld"')
 
diff --git a/tools/sdk/ld/eagle.flash.16m14m.ld b/tools/sdk/ld/eagle.flash.16m14m.ld
index ccce5ddc39..cba3a57b46 100644
--- a/tools/sdk/ld/eagle.flash.16m14m.ld
+++ b/tools/sdk/ld/eagle.flash.16m14m.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x40400000 );
-PROVIDE ( _SPIFFS_end = 0x411FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x2000 );
+PROVIDE ( _FS_start = 0x40400000 );
+PROVIDE ( _FS_end = 0x411FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x2000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.16m15m.ld b/tools/sdk/ld/eagle.flash.16m15m.ld
index 93b30d219b..4e4c30a8df 100644
--- a/tools/sdk/ld/eagle.flash.16m15m.ld
+++ b/tools/sdk/ld/eagle.flash.16m15m.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x40300000 );
-PROVIDE ( _SPIFFS_end = 0x411FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x2000 );
+PROVIDE ( _FS_start = 0x40300000 );
+PROVIDE ( _FS_end = 0x411FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x2000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.1m.ld b/tools/sdk/ld/eagle.flash.1m.ld
index b198d8b737..57c8ab2da6 100644
--- a/tools/sdk/ld/eagle.flash.1m.ld
+++ b/tools/sdk/ld/eagle.flash.1m.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xf9ff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x402FB000 );
-PROVIDE ( _SPIFFS_end = 0x402FB000 );
-PROVIDE ( _SPIFFS_page = 0x0 );
-PROVIDE ( _SPIFFS_block = 0x0 );
+PROVIDE ( _FS_start = 0x402FB000 );
+PROVIDE ( _FS_end = 0x402FB000 );
+PROVIDE ( _FS_page = 0x0 );
+PROVIDE ( _FS_block = 0x0 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.1m128.ld b/tools/sdk/ld/eagle.flash.1m128.ld
index c6ab02feb6..729f613986 100644
--- a/tools/sdk/ld/eagle.flash.1m128.ld
+++ b/tools/sdk/ld/eagle.flash.1m128.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xd9ff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x402DB000 );
-PROVIDE ( _SPIFFS_end = 0x402FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x1000 );
+PROVIDE ( _FS_start = 0x402DB000 );
+PROVIDE ( _FS_end = 0x402FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x1000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.1m144.ld b/tools/sdk/ld/eagle.flash.1m144.ld
index 0da7edf593..e644897c49 100644
--- a/tools/sdk/ld/eagle.flash.1m144.ld
+++ b/tools/sdk/ld/eagle.flash.1m144.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xd5ff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x402D7000 );
-PROVIDE ( _SPIFFS_end = 0x402FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x1000 );
+PROVIDE ( _FS_start = 0x402D7000 );
+PROVIDE ( _FS_end = 0x402FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x1000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.1m160.ld b/tools/sdk/ld/eagle.flash.1m160.ld
index e927985243..08b54f51f1 100644
--- a/tools/sdk/ld/eagle.flash.1m160.ld
+++ b/tools/sdk/ld/eagle.flash.1m160.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xd1ff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x402D3000 );
-PROVIDE ( _SPIFFS_end = 0x402FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x1000 );
+PROVIDE ( _FS_start = 0x402D3000 );
+PROVIDE ( _FS_end = 0x402FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x1000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.1m192.ld b/tools/sdk/ld/eagle.flash.1m192.ld
index c45d7e6afe..77ddac33db 100644
--- a/tools/sdk/ld/eagle.flash.1m192.ld
+++ b/tools/sdk/ld/eagle.flash.1m192.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xc9ff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x402CB000 );
-PROVIDE ( _SPIFFS_end = 0x402FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x1000 );
+PROVIDE ( _FS_start = 0x402CB000 );
+PROVIDE ( _FS_end = 0x402FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x1000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.1m256.ld b/tools/sdk/ld/eagle.flash.1m256.ld
index 37bce0c948..b197a2b1cc 100644
--- a/tools/sdk/ld/eagle.flash.1m256.ld
+++ b/tools/sdk/ld/eagle.flash.1m256.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xb9ff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x402BB000 );
-PROVIDE ( _SPIFFS_end = 0x402FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x1000 );
+PROVIDE ( _FS_start = 0x402BB000 );
+PROVIDE ( _FS_end = 0x402FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x1000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.1m512.ld b/tools/sdk/ld/eagle.flash.1m512.ld
index f2df1a0734..811f7a1505 100644
--- a/tools/sdk/ld/eagle.flash.1m512.ld
+++ b/tools/sdk/ld/eagle.flash.1m512.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0x79ff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x4027B000 );
-PROVIDE ( _SPIFFS_end = 0x402FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x2000 );
+PROVIDE ( _FS_start = 0x4027B000 );
+PROVIDE ( _FS_end = 0x402FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x2000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.1m64.ld b/tools/sdk/ld/eagle.flash.1m64.ld
index eacaa8c0eb..92447c0f9c 100644
--- a/tools/sdk/ld/eagle.flash.1m64.ld
+++ b/tools/sdk/ld/eagle.flash.1m64.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xe9ff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x402EB000 );
-PROVIDE ( _SPIFFS_end = 0x402FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x1000 );
+PROVIDE ( _FS_start = 0x402EB000 );
+PROVIDE ( _FS_end = 0x402FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x1000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.2m.ld b/tools/sdk/ld/eagle.flash.2m.ld
index be54402b6e..00b11a0734 100644
--- a/tools/sdk/ld/eagle.flash.2m.ld
+++ b/tools/sdk/ld/eagle.flash.2m.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x403FB000 );
-PROVIDE ( _SPIFFS_end = 0x403FB000 );
-PROVIDE ( _SPIFFS_page = 0x0 );
-PROVIDE ( _SPIFFS_block = 0x0 );
+PROVIDE ( _FS_start = 0x403FB000 );
+PROVIDE ( _FS_end = 0x403FB000 );
+PROVIDE ( _FS_page = 0x0 );
+PROVIDE ( _FS_block = 0x0 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.2m128.ld b/tools/sdk/ld/eagle.flash.2m128.ld
index ba04e939c0..1e96769b9a 100644
--- a/tools/sdk/ld/eagle.flash.2m128.ld
+++ b/tools/sdk/ld/eagle.flash.2m128.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x403E0000 );
-PROVIDE ( _SPIFFS_end = 0x403FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x1000 );
+PROVIDE ( _FS_start = 0x403E0000 );
+PROVIDE ( _FS_end = 0x403FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x1000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.2m1m.ld b/tools/sdk/ld/eagle.flash.2m1m.ld
index 08fb1b152a..2e0cbe704a 100644
--- a/tools/sdk/ld/eagle.flash.2m1m.ld
+++ b/tools/sdk/ld/eagle.flash.2m1m.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x40300000 );
-PROVIDE ( _SPIFFS_end = 0x403FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x2000 );
+PROVIDE ( _FS_start = 0x40300000 );
+PROVIDE ( _FS_end = 0x403FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x2000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.2m256.ld b/tools/sdk/ld/eagle.flash.2m256.ld
index 50610cc42b..4b59a1b15a 100644
--- a/tools/sdk/ld/eagle.flash.2m256.ld
+++ b/tools/sdk/ld/eagle.flash.2m256.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x403C0000 );
-PROVIDE ( _SPIFFS_end = 0x403FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x1000 );
+PROVIDE ( _FS_start = 0x403C0000 );
+PROVIDE ( _FS_end = 0x403FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x1000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.2m512.ld b/tools/sdk/ld/eagle.flash.2m512.ld
index 2d8f3e78a6..7f3b264ea3 100644
--- a/tools/sdk/ld/eagle.flash.2m512.ld
+++ b/tools/sdk/ld/eagle.flash.2m512.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x40380000 );
-PROVIDE ( _SPIFFS_end = 0x403FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x2000 );
+PROVIDE ( _FS_start = 0x40380000 );
+PROVIDE ( _FS_end = 0x403FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x2000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.4m.ld b/tools/sdk/ld/eagle.flash.4m.ld
index b0f9b94fa2..f77c95ae1e 100644
--- a/tools/sdk/ld/eagle.flash.4m.ld
+++ b/tools/sdk/ld/eagle.flash.4m.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x405FB000 );
-PROVIDE ( _SPIFFS_end = 0x405FB000 );
-PROVIDE ( _SPIFFS_page = 0x0 );
-PROVIDE ( _SPIFFS_block = 0x0 );
+PROVIDE ( _FS_start = 0x405FB000 );
+PROVIDE ( _FS_end = 0x405FB000 );
+PROVIDE ( _FS_page = 0x0 );
+PROVIDE ( _FS_block = 0x0 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.4m1m.ld b/tools/sdk/ld/eagle.flash.4m1m.ld
index 74bf04bc3d..9feb6039de 100644
--- a/tools/sdk/ld/eagle.flash.4m1m.ld
+++ b/tools/sdk/ld/eagle.flash.4m1m.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x40500000 );
-PROVIDE ( _SPIFFS_end = 0x405FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x2000 );
+PROVIDE ( _FS_start = 0x40500000 );
+PROVIDE ( _FS_end = 0x405FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x2000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.4m2m.ld b/tools/sdk/ld/eagle.flash.4m2m.ld
index 6eb95b1be9..5fe26ccd9d 100644
--- a/tools/sdk/ld/eagle.flash.4m2m.ld
+++ b/tools/sdk/ld/eagle.flash.4m2m.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x40400000 );
-PROVIDE ( _SPIFFS_end = 0x405FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x2000 );
+PROVIDE ( _FS_start = 0x40400000 );
+PROVIDE ( _FS_end = 0x405FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x2000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.4m3m.ld b/tools/sdk/ld/eagle.flash.4m3m.ld
index 37acf69daf..f4e9e2461d 100644
--- a/tools/sdk/ld/eagle.flash.4m3m.ld
+++ b/tools/sdk/ld/eagle.flash.4m3m.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x40300000 );
-PROVIDE ( _SPIFFS_end = 0x405FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x2000 );
+PROVIDE ( _FS_start = 0x40300000 );
+PROVIDE ( _FS_end = 0x405FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x2000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.512k.ld b/tools/sdk/ld/eagle.flash.512k.ld
index 65c5444bd8..4f421ab8eb 100644
--- a/tools/sdk/ld/eagle.flash.512k.ld
+++ b/tools/sdk/ld/eagle.flash.512k.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0x79ff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x4027B000 );
-PROVIDE ( _SPIFFS_end = 0x4027B000 );
-PROVIDE ( _SPIFFS_page = 0x0 );
-PROVIDE ( _SPIFFS_block = 0x0 );
+PROVIDE ( _FS_start = 0x4027B000 );
+PROVIDE ( _FS_end = 0x4027B000 );
+PROVIDE ( _FS_page = 0x0 );
+PROVIDE ( _FS_block = 0x0 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.512k128.ld b/tools/sdk/ld/eagle.flash.512k128.ld
index fbd64a6364..7a3c573af0 100644
--- a/tools/sdk/ld/eagle.flash.512k128.ld
+++ b/tools/sdk/ld/eagle.flash.512k128.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0x59ff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x4025B000 );
-PROVIDE ( _SPIFFS_end = 0x4027B000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x1000 );
+PROVIDE ( _FS_start = 0x4025B000 );
+PROVIDE ( _FS_end = 0x4027B000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x1000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.512k32.ld b/tools/sdk/ld/eagle.flash.512k32.ld
index 0e1d86fc88..1334b8d8e4 100644
--- a/tools/sdk/ld/eagle.flash.512k32.ld
+++ b/tools/sdk/ld/eagle.flash.512k32.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0x71ff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x40273000 );
-PROVIDE ( _SPIFFS_end = 0x4027B000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x1000 );
+PROVIDE ( _FS_start = 0x40273000 );
+PROVIDE ( _FS_end = 0x4027B000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x1000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.512k64.ld b/tools/sdk/ld/eagle.flash.512k64.ld
index 496c47d23b..fdea4da8e3 100644
--- a/tools/sdk/ld/eagle.flash.512k64.ld
+++ b/tools/sdk/ld/eagle.flash.512k64.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0x69ff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x4026B000 );
-PROVIDE ( _SPIFFS_end = 0x4027B000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x1000 );
+PROVIDE ( _FS_start = 0x4026B000 );
+PROVIDE ( _FS_end = 0x4027B000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x1000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.8m6m.ld b/tools/sdk/ld/eagle.flash.8m6m.ld
index b65d46751a..c0cd9ed40a 100644
--- a/tools/sdk/ld/eagle.flash.8m6m.ld
+++ b/tools/sdk/ld/eagle.flash.8m6m.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x40400000 );
-PROVIDE ( _SPIFFS_end = 0x409FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x2000 );
+PROVIDE ( _FS_start = 0x40400000 );
+PROVIDE ( _FS_end = 0x409FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x2000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"
diff --git a/tools/sdk/ld/eagle.flash.8m7m.ld b/tools/sdk/ld/eagle.flash.8m7m.ld
index 9ec7cc128b..c5f7c26552 100644
--- a/tools/sdk/ld/eagle.flash.8m7m.ld
+++ b/tools/sdk/ld/eagle.flash.8m7m.ld
@@ -14,9 +14,9 @@ MEMORY
   irom0_0_seg :                         org = 0x40201010, len = 0xfeff0
 }
 
-PROVIDE ( _SPIFFS_start = 0x40300000 );
-PROVIDE ( _SPIFFS_end = 0x409FB000 );
-PROVIDE ( _SPIFFS_page = 0x100 );
-PROVIDE ( _SPIFFS_block = 0x2000 );
+PROVIDE ( _FS_start = 0x40300000 );
+PROVIDE ( _FS_end = 0x409FB000 );
+PROVIDE ( _FS_page = 0x100 );
+PROVIDE ( _FS_block = 0x2000 );
 
 INCLUDE "local.eagle.app.v6.common.ld"