ESP32 File System

ESP32: Using the Internal Flash File System

The ESP32 contains a lightweight filesystem specially design for microcontrollers called Serial Peripheral Interface Flash File System. This File System, also known as SPIFFS for its initials, is implemented on top of a Flash chip connected directly to the SPI bus.

SPIFFS supports basic operations on files, like opening, close, read and write, and a very rudimentary implementation of directories. You will find that several project uses this File System in order to serve pages (and files) in a Web Server.

Requirements

First of all we need to install a few dependencies. The most important is the File System uploader tool, which can be used from the Arduino IDE, Visual Studio Code, or command line. The steps for performing this installation can be found at project repository on GitHub.

If you are using Visual Studio Code, as I do, install the following extension: ESP8266FS. From the command line:

code --install-extension vscode-esp8266fs

Do not forget to install Arduino-ESP32 Core https://github.com/espressif/arduino-esp32#installation-instructions

Project configuration

When using the Arduino IDE, just selecting the board in the Board Manager will do the job. But when using Visual Studio Code, we have to set some values on .vscode/arduino.json.

{
    "sketch": "main.ino",
    "board": "esp32:esp32:featheresp32",
    "configuration": "FlashFreq=80m,UploadSpeed=921600,DebugLevel=none,PartitionScheme=default,FlashSize=4MB,FlashMode=keep",
    "port": "/dev/tty.SLAB_USBtoUART",
    "output": "output"
}

With this configuration 1M of Flash will be used for SPIFFS and the rest (3M out of 4M) for your code.

Testing the plugin configuration

In Arduino IDE using the Data Uploader is straightforward. But on Visual Studio Code the plugin should be configured carefully in order to work. The following is my Visual Studio Code configuration (settings.json) which is quite verbose for testing purposes.

{
   ...
    "esp8266fs.espota.debug": true,
    "esp8266fs.esptool.executable": "/Users/kimi/Documents/Arduino/hardware/espressif/esp32/tools/esptool.py",
    "esp8266fs.esptool.verbosity": "vvv",
    "esp8266fs.logLevel": "debug",
    "esp8266fs.mkspiffs.executable": "/Users/kimi/Documents/Arduino/hardware/espressif/esp32/tools/mkspiffs/mkspiffs",
    "esp8266fs.preferencesPath": "/Users/kimi/Library/Arduino15",
    "esp8266fs.arduinoUserPath": "/Users/kimi/Documents/Arduino",
    "esp8266fs.spiffsImage": "./spiffs.image",
    "esptool.py.spi_connection": "SPI",
    "esptool.py.compress": "true",
    "esptool.py.verify": "false",
   ...
}
ESP32 has not been installed correctly

Testing the Data Upload can be done by List SPIFFS command. cmd + shift P and select ESP8266FS: List SPIFFS. If you get an error saying

ESP32 has not been installed correctly – see https://github.com/espressif/arduino-esp32

Take into consideration that the Path of the ESP32 Tool is generated by splitting the Board key on .vscode/arduino.json in order to get three parameters: Package, Architecture and Board (ref: https://github.com/kash4kev/vscode-esp8266fs/…/esp8266fs.js#L467).

As my board is an Adafruit HUZZAH32, and my board config is

{
   ...
   "board": "esp32:esp32:featheresp32",
   ...
}

the three key parameters will be:

{
   "package": "esp32",
   "architecture": "esp32",
   "board": "featheresp32",
}

This parameters will be used by the ESP8266FS plugin to create the path where ESP tools are installed. In my setup it will result in /Users/kimi/Documents/Arduino/hardware/esp32/esp32. But, based on the ESP32 Arduino core tutorial, my hardware definition is installed on /Users/kimi/Documents/Arduino/hardware/espressif/esp32.

My workaround to this issue was to create a symbolic link so the esp32 directory was points to espressif.

ln -s espressif esp32
Couldn’t find match for argument

Another common error is Couldn’t find match for argument when running mkspiffs, which is a tool found in the ESP32 Arduino Core SDK that handles the creation, upload and download (among other responsibilities) of images.

This issue is already reported on GitHub at https://github.com/kash4kev/vscode-esp8266fs/issues/15. The workaround as explain there, is to disable the configuration esp8266fs.mkspiffs.allFiles.

Packing information into an Image

In your project directory, next to your main Arduino file, create a directory called data. All files stored in that folder will be transfer to your ESP32 microcontroller when uploading the File System.

All our data files are not transferred one by one to the ESP32, but inside an Image. In order to create that image we should pack those files using mkspiffs. The ESP8266FS extension has a command for doing that, but be can also use the command line tool.

The benefits of using the command line tool are clear, the operation can be automated inside a script.

/<Path To Arduino>/hardware/espressif/esp32/tools/mkspiffs/mkspiffs --create /<Your Arduino Project Directory>/data --size 0x30000 --page 256 --block 4096 /<Your Arduino Project Directory>/spiffs.image

Uploading files

Remember that uploading files from your Computer will replace the files present in your ESP32.

You will also find a handy command in the Visual Studio Code extension for uploading your currently created image. But if you want to do that using the command line, the following execution will do the trick.

/<Path To Arduino>/hardware/espressif/esp32/tools/esptool/esptool -vvv -ca 0x3D0000 -cd  -cp /dev/tty.SLAB_USBtoUART -cb 921600 -cf /<Your Arduino Project Directory>/spiffs.image

Where /dev/tty.SLAB_USBtoUART is the USB Serial port where I have my ESP32 connected. The result of running this command may look like

--- Uploading SPIFFS file with esptool.py ---
Python Executable: "python"
SPIFFS Uploading Image... (/Users/kimi/Works/eaceto.dev/embedded/ESP32/P1/spiffs.image)
  [SPIFFS] Python   : python
  [SPIFFS] EspTool  : /Users/kimi/Documents/Arduino/hardware/espressif/esp32/tools/esptool.py
  [SPIFFS] address  : 0x290000
  [SPIFFS] port     : /dev/tty.SLAB_USBtoUART
  [SPIFFS] speed    : 921600
  [SPIFFS] before   : default_reset
  [SPIFFS] after    : hard_reset
  [SPIFFS] flashMode: keep
  [SPIFFS] flashFreq: 80m
  [SPIFFS] flashSize: 4MB
Running: python /Users/kimi/Documents/Arduino/hardware/espressif/esp32/tools/esptool.py --chip esp32 --baud 921600 --port /dev/tty.SLAB_USBtoUART --before default_reset --after hard_reset write_flash --flash_mode keep --flash_freq 80m --flash_size 4MB 0x290000 /Users/kimi/Works/eaceto.dev/embedded/ESP32/P1/spiffs.image
esptool.py v2.8
Serial port /dev/tty.SLAB_USBtoUART
Connecting....
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, Coding Scheme None
Crystal is 40MHz
MAC: 24:0a:c4:0c:94:78
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
Configuring flash size...
Compressed 196608 bytes to 537...
Writing at 0x00290000... (100 %)
Wrote 196608 bytes (537 compressed) at 0x00290000 in 0.0 seconds (effective 136297.5 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
ESP8266 SPIFFS interface finished.

Verifying what it’s inside an image

If you want to check which files are included in an image file, you can list them by running

<Path To Arduino>/hardware/espressif/esp32/tools/mkspiffs/mkspiffs --list --page 256 --block 4096 <Path to>/spiffs.image

Downloading an image form the ESP32 to your Computer

May be one of the most important steps when writing an image to the ESP32 is verifying that it was correctly done. For doing this you have to find how many bytes where written the uploading the image to the ESP32. This information can be found on the output of the write_flash operation. And as important as how many it is, where. This information it is also present in the same debug output.

In my previous example this two values where 196608 bytes and starting address 0x290000. So the command to execute in order to read that segment of Flash is:

<Path To Arduino>/hardware/espressif/esp32/tools/esptool.py --chip esp32 --baud 921600 --port /dev/tty.SLAB_USBtoUART read_flash  0x290000 196608 downloaded.image

Output

iFi, BT, Dual Core, Coding Scheme None
Crystal is 40MHz
MAC: 24:0a:c4:0c:94:78
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
196608 (100 %)
196608 (100 %)
Read 196608 bytes at 0x290000 in 2.5 seconds (641.6 kbit/s)...
Hard resetting via RTS pin...

Using the command explained in Verifying what’s inside the image we can see which files are inside the image

<Path To Arduino>/hardware/esp32/esp32/tools/mkspiffs/mkspiffs --list --page 256 --block 4096 downloaded.image

Output

159 /config_test.json

Next steps…

In the following post I will show how to read a JSON file and obtain a configuration parameter that will be use to configure the WiFi network to which our ESP32 will be connected.

Stay tuned!

Software Framework comes to rescue: FAT32

Embedded applications make intense use of information these days, both for reading configurations and writing logs and process’ results. Developers tend to use built-in FLASH or small EEPROM memories to archive these tasks but as information gets complex, systems’ features grow and programming is done in high level languages, the need of a reliable and flexible solution takes more importance.
Is in this situation when embedded systems designers incorporates technologies available in personal computers, servers and mobile devices. Using mass storage devices like USB keys, USB-SATA disks and memory cards is a great solution for several problems including portability, capacity and reliability.
The Atmel’s AVR32 Software Framework incorporates low level drivers for SD/MMC cards, AT45DB memories and a USB mini Host driver capable of communicating with USB keys and disks. Moreover FAT12/16/32 is present as a high level API that can be connected to any low level driver.
Creating a file inside a directory, and writing something inside is as simple as this

For making use of these drivers and components the project must be configured with the following features:

  • Drivers
    • PM
    • SPI
    • USART
    • GPIO
    • INTC
  • Components
    • SD/MMC Memory Card
  • Services
    • FAT File System
    • Memory Control Access Services

Then it is necessary to set CONFIG/conf_access.h properly to activate the SD/MMC logic unit.

/*! name Activation of Logical Unit Numbers
*/
//! @{
#define LUN_0 DISABLE //!< On-Chip Virtual Memory.
#define LUN_1 DISABLE //!< AT45DBX Data Flash.
#define LUN_2 ENABLE //!< SD/MMC Card over SPI.
#define LUN_3 DISABLE
#define LUN_4 DISABLE
#define LUN_5 DISABLE
#define LUN_6 DISABLE
#define LUN_7 DISABLE
#define LUN_USB DISABLE //!< Host Mass-Storage Memory.
//! @}


/*! name Activation of Interface Features
*/
//! @{
#define ACCESS_USB DISABLED //!< MEM <-> USB interface.
#define ACCESS_MEM_TO_RAM ENABLED //!< MEM <-> RAM interface.
#define ACCESS_STREAM DISABLED //!< Streaming MEM <-> MEM interface.
#define ACCESS_STREAM_RECORD DISABLED //!< Streaming MEM <-> MEM interface in record mode.
#define ACCESS_MEM_TO_MEM DISABLED //!< MEM <-> MEM interface.
#define ACCESS_CODEC DISABLED //!< Codec interface.
//! @}

And then call this SPI initialization code from your main code:

/*! brief Initializes SD/MMC resources: GPIO, SPI and SD/MMC.
*/
static void sd_mmc_resources_init(void) {
// GPIO pins used for SD/MMC interface
static const gpio_map_t SD_MMC_SPI_GPIO_MAP = { { SD_MMC_SPI_SCK_PIN,
SD_MMC_SPI_SCK_FUNCTION }, // SPI Clock.
{ SD_MMC_SPI_MISO_PIN, SD_MMC_SPI_MISO_FUNCTION }, // MISO.
{ SD_MMC_SPI_MOSI_PIN, SD_MMC_SPI_MOSI_FUNCTION }, // MOSI.
{ SD_MMC_SPI_NPCS_PIN, SD_MMC_SPI_NPCS_FUNCTION } // Chip Select NPCS.
};
// SPI options.
spi_options_t spiOptions = {
.reg = SD_MMC_SPI_NPCS,
.baudrate = SD_MMC_SPI_MASTER_SPEED, // Defined in conf_sd_mmc_spi.h.
.bits = SD_MMC_SPI_BITS, // Defined in conf_sd_mmc_spi.h.
.spck_delay = 0, .trans_delay = 0, .stay_act = 1, .spi_mode = 0,
.modfdis = 1 };
// Assign I/Os to SPI.
gpio_enable_module(SD_MMC_SPI_GPIO_MAP, sizeof(SD_MMC_SPI_GPIO_MAP)
/ sizeof(SD_MMC_SPI_GPIO_MAP[0]));
// Initialize as master.
spi_initMaster(SD_MMC_SPI, &spiOptions);
// Set SPI selection mode: variable_ps, pcs_decode, delay.
spi_selectionMode(SD_MMC_SPI, 0, 0, 0);
// Enable SPI module.
spi_enable(SD_MMC_SPI);
// Initialize SD/MMC driver with SPI clock (PBA).
sd_mmc_spi_init(spiOptions, PBA_HZ);
}

iPhone App for AVR Development

There is an infinite number of AVR website that has configuration wizards for UART, Timers and SPI, but I didn’t find an iPhone application that can do something similar. So I developed an iOS application that lets you setup the AVR SPI easily, email this configuration, and also understand what each bit of the SPI registers does. The application is called AVR ISP and soon it will be available in the App Store. Hopefully the next version (1.1) will have code generation in C and ASM, and not only for the setup, but also including functions for handling the SPI interface.
Here I show some of the screenshots of the app. And as soon as Apple publishes the app in the Store I’ll upload the link.

UPDATED: Apple has approved AVR SPI and now it’s ready for download from the App Store. You can find it by just looking for “AVR SPI” or using this link to iTunes