LittleFS is a compact and economical file system for ARM microcontrollers as part of mbed os. Fast start

image altIn December 2017, ARM introduced the public a new version of the operating system for ARM microcontrollers “arm mbed os v.5.7” (version 5.7.3 was released on January 17, 2018), which received an integrated author’s file system called “LittleFileSystem”, or just LittleFS. I propose today to talk about this new product.

The main advantages of the LittleFS file system are:

  • Low demands on the resources of the microcontroller. The code does not use recursive calls and works without dynamic memory allocation. In "LittleFS" the amount of RAM consumed always remains constant, regardless of what volumes are written to the drive, and to the volume of the drive itself;
  • Availability of software tools for leveling media wear (the so-called wear leveling) to minimize reuse of media blocks;
  • Resistance to power failures. This feature is a regular consideration of the power failure of the media, and uses the mechanism of "copy-on-write (COW)", in which the data is not overwritten, but stored in a new place.

The system supports a complete set of POSIX functions for working with files and directories.

Let me remind you that the file system is integrated into the arm mbed os operating system, which is actively developed and supported by the manufacturer , so I will have to say a little about this OS for ARM microcontrollers, despite a certain amount of materials about it published on the resource. Therefore, here I am a little distracted by mbed os , and those who already have experience with it can scroll through the next few paragraphs.

The operating system is written in C / C ++, and the main advantage of the OS, and its main drawback, in my opinion (no matter how paradoxical this may sound), is a really high level of abstraction from the hardware.

Those who, when working with STM32 microcontrollers (I’m talking to them specifically) used the HAL and SPL libraries (and even more so those who piled about registers manually), remember how many lines of code needed to be created for configuration, for example, interruptions on a falling edge at pin PA_5 : enable clocking a port, configure the operating mode of a particular output, configure the actual interrupt, enable interrupt — and only then describe the handler.

At mbed os everything is made much simpler.

        InterruptIn irq_button1(PA_5);
 	irq_button1.fall(led_blink);

First, I gave the name to the interrupt and indicated the output, by changing the state of which I want to cause an interrupt. In the second line, I indicated the sign by which I need to go to the handler ( fall , falling edge of the signal), and indicated the name of the interrupt handler itself ( led_blink ).

A high level of abstraction is convenient, but the novice beginner does not understand at all what is happening in the microcontroller at the moment, and that in fact one line of code causes tens of values ​​to be written to dozens of registers. And this, of course, is not very good.

The easiest way to get started with mbed os is to head to the mbed.com portal, and register there as a developer (elementary registration with confirmation by e-mail). From now on, an online compiler is available to you, which will play an important role for the start. In short, what we are going to do: as a start and to make it easier for us, we will take any example that includes mbed os from the repository on the os.mbed.com portal , and then import it as a project for our IDE and ideally , for our microcontroller. The OS, by the way, is also a marketing tool, and for its use ARM recommends the so-called “ mbed-enabled"Debug boards, and at first glance it might seem that only boards from the catalog on the site are suitable for mbed os. But this, of course, is not so.

image

In the upper right corner, we have a button for selecting the debug board with which we are going to work (of course, it is assumed that we purchased one of the boards in the catalog).

image

After clicking on the button, a window for selecting a specific board will appear. There, in the window, there will be a button with a big green plus “ Add Platform ”.

image

A window for selecting platforms will open in a separate browser window, among which we will choose “ STMicroelectronics ”, and select a board on which the same microcontroller is installed as the one we are going to work with. I was lucky - on my home-made debug board there is a STM32F103RBT6 microcontroller , as well as on a Nucleo-F103RB board .

In the near future I plan to write a little material on how to implement a project with arm mbed os for any arbitrary MK.

On the page with the description of the board that we selected in the selector, on the right, click the " Add to your Mbed Compiler " button .

image

Immediately below, examples are located. We will choose the smallest and simplest - " Nucleo_blink_led ".

image

In the window that opens, in the upper right, click the “ Import Into Compiler ” button .

image

Your online compiler will open and ask you to confirm the import of the project. After confirmation, we will see our project in the project tree on the left.

image

The only thing left is to import the project under our desktop IDE. The online compiler supports a lot of IDEs, including IAR for ARM , KEIL uVision , CooCox CoIDE , and many other environments. I use the “ IAR for ARM v.8.20.1environment , but ARM for working with mbed os recommends IAR version no lower than 7.5.

On the right, in the "Program Details" block, click on the " Export " button .

image

In the pop-up window “Export program”, select the platform and development environment that is installed on our computer.

image

After clicking on the “ Export ” button , the online compiler will think for a bit, and the archive with the project will start downloading in the browser. We unpack it in any place convenient for us, open the development environment, open the project, clear out everything that is unnecessary for us in the main file “main.cpp”, and start working.

By the way, everything in the same “ Program details ” block , before exporting the project, you can evaluate the degree of project load on the built-in flash and RAM of our target controller. Open the Build tab .

image

On this, perhaps, I will end the already protracted excursion into a quick start with mbed os , and return to the conversation about LittleFS .

To work correctly with the file system, we must include the header files “ LittleFileSystem.h ” in the project and, necessarily, the header file of the medium. I suppose working with an SD card, so I include the file “ SDBlockDevice.h ” in the project . Among other devices, MBRBlockDevice , HeapBlockDevice (very convenient for training, since it does not require the presence of physical media), and other types of devices are available.

// Block device
#include "SDBlockDevice.h"
// File system
#include "LittleFileSystem.h"

So, I propose to consider our sequence of actions when building a start-up project for working with LittleFS . First we need to create the device objects (and initialize its outputs) and the file system. Then we mount the file system, create a couple of files on the media, open the root directory and read from it the list of files contained in it.

Let's pay attention to the class inheritance tree for working with streams implemented in mbed os .

image

As you can see, for working with files we have implemented a whole class “ File ”. To work with directories there is a class " Dir ". Their description can be found on the pages File and Dir .

However, in practice, one interesting nuance appeared.
When trying to declare an object of class “ File ” and use the File method (FileSystem * fs, const char * path, int flags = O_RDONLY) , designed to create files, the compiler refused to recognize the object.

image

And when using standard methods of working with streams (" stdio.h ") to create a file, and when trying to further work with it using the methods of the " File " class , such as, for example, read or write , the operating system fell into fun mbed_die () , the processor of an unrecoverable system error.

Therefore, so far I will limit myself to writing a letter to the ARM support , and we will work with files using stdio tools , which, perhaps, will turn out to be more familiar to someone. A full description of the stdio functionality can be found, for example, on this site .

So the code:

#include "mbed.h"
#include 
#include 
// Block device
#include "SDBlockDevice.h"
// File systems
#include "LittleFileSystem.h"
SDBlockDevice bdsd(PB_15, PB_14, PB_13, PB_12); // mosi, miso, sclk, cs
LittleFileSystem fs("fs");
/******************************************************************************/
// main() runs in its own thread in the OS
int main() {
    printf("--- Mbed OS filesystem example ---\n");
    // Try to mount the filesystem
    printf("Mounting the filesystem... ");
    fflush(stdout);
    int err = fs.mount(&bdsd);
    printf("%s\n", (err ? "Fail :(" : "OK"));
    if (err) {
        // Reformat if we can't mount the filesystem
        // this should only happen on the first boot
        printf("No filesystem found, formatting... ");
        fflush(stdout);
        err = fs.reformat(&bdsd);
        printf("%s\n", (err ? "Fail :(" : "OK"));
        if (err) {
            error("error: %s (%d)\n", strerror(-err), err);
        }
    }
     // Open the LittleFS.txt file
    FILE *f = fopen("/fs/LittleFS.txt", "r+");
    printf("%s\n", (!f ? "Fail :(" : "OK"));
      if (!f) {
        // Create the LittleFS file if it doesn't exist
        printf("No file found, creating a new file... ");
        fflush(stdout);
        f = fopen("/fs/LittleFS.txt", "w+");
        printf("%s\n", (!f ? "Fail :(" : "OK"));
          if (!f) {
            error("error: %s (%d)\n", strerror(errno), -errno);
              }
      }
    printf("\r Closing \"/fs/LittleFS.txt\"... ");
    fflush(stdout);
    err = fclose(f);
    printf("%s\n", (err < 0 ? "Fail :(" : "OK"));
    if (err < 0) {
        error("error: %s (%d)\n", strerror(errno), -errno);
    }
    // Open the Habrahabr.txt file
    f = fopen("/fs/Habrahabr.txt", "r+");
    printf("%s\n", (!f ? "Fail :(" : "OK"));
      if (!f) {
        // Create the LittleFS file if it doesn't exist
        printf("No file found, creating a new file... ");
        fflush(stdout);
        f = fopen("/fs/Habrahabr.txt", "w+");
        printf("%s\n", (!f ? "Fail :(" : "OK"));
          if (!f) {
            error("error: %s (%d)\n", strerror(errno), -errno);
              }
      }
    printf("\r Closing \"/fs/Habrahabr.txt\"... ");
    fflush(stdout);
    err = fclose(f);
    printf("%s\n", (err < 0 ? "Fail :(" : "OK"));
    if (err < 0) {
        error("error: %s (%d)\n", strerror(errno), -errno);
    }
    // Display the root directory
    printf("Opening the root directory... ");
    fflush(stdout);
    DIR *d = opendir("/fs/");
    printf("%s\n", (!d ? "Fail :(" : "OK"));
    if (!d) {
        error("error: %s (%d)\n", strerror(errno), -errno);
    }
    printf("root directory:\n");
    while (true) {
        struct dirent *e = readdir(d);
        if (!e) {
            break;
        }
        printf("    %s\n", e->d_name);
    }
    printf("Closing the root directory... ");
    fflush(stdout);
    err = closedir(d);
    printf("%s\n", (err < 0 ? "Fail :(" : "OK"));
    if (err < 0) {
        error("error: %s (%d)\n", strerror(errno), -errno);
    }
    // Unmounting
    printf("Unmounting... ");
    fflush(stdout);
    err = fs.unmount();
    printf("%s\n", (err < 0 ? "Fail :(" : "OK"));
    if (err < 0) {
        error("error: %s (%d)\n", strerror(-err), err);
    }
    printf("LittleFS tested successfully!\n");
}

First, we create objects of classes SDBlockDevice (and then initialize it by passing the names of the findings in a strict sequence in accordance with the purpose: mosi , A miso , sclk , cs ) and LittleFileSystem (fs) .
Then, already in the “ main ” function , we try to mount the file system (which we, of course, will not work when using the new SD card). Format the media and mount the file system again.

Next, we try to open the files “ LittleFS.txt ” and “ Habrahabr.txt ” one by one, and without finding them, we create it. After opening the file must be closed.

After successful operations with files, we read the contents of the root directory and display the file names in the input / output terminal. Close the directory.

We dismantle the file system and report to the terminal about the successful verification of the file system.

And here is a screenshot of the messages in the debugger terminal.

image

So, today we talked about a promising and, in my opinion, a very modern file system for microcontrollers called " LittleFileSystem ".

When the issue is resolved using "native" means of working with files and directories (using the File and Dir classes ), I promise an update to the article.
Thank you for attention.

UPDATE


The material did not have time to go through pre-moderation, as it dawned on me - I incorrectly tried to use methods of the File class . To create (or open an existing) file, you must use the open method (FileSystem * fs, const char * path, int flags = O_RDONLY); .
As a result, the code will be supplemented with new objects of the File and Dir classes for working with files and directories, for example,
File fhandle;
Dir dhandle;


So the whole example of working with the help of files and directories integrated into the mbed os OS will be slightly transformed. Also, in addition to the fact that we create and / or open files, let's already write something there and count. For these purposes, we will create two buffers: from one, a line will be written to the file, and in the second we consider the contents of the file.
#define BLOCK_SIZE 16
char block[BLOCK_SIZE] = "tasty cake";
char rblock[BLOCK_SIZE];


So, the code for operations with the contents of the SD card (meanwhile, how we mount and dismantle the file system).

/******************************************************************************/    
   err = fhandle.open(&fs,"testing.txt", (O_RDWR|O_TRUNC));
   if (err<0){
    printf("No file found, creating a new file...\n");
    fflush(stdout);
    err = fhandle.open(&fs,"testing.txt", (O_RDWR|O_CREAT));}
   err = fhandle.write(block,10);
   if (err<0) printf("Writing error!\n"); 
   printf("Written bytes (%d) ones\n", err);
   fhandle.rewind();                            //go to the file beginning
   err = fhandle.read(rblock,10);
   if (err<0) printf("Reading error!\n");
   printf("Read bytes (%d) ones\n", err);
   printf("Read from file: %s\n", rblock);
   err = fhandle.size();
   printf("File size (%d) bytes\n", err);
   printf("Closing file...");
   err = fhandle.close();
   if (err<0) printf("Closing file failure!\n");
   else printf("...OK\n");
/******************************************************************************/    
    // Display the root directory
    printf("Opening the root directory... ");
    fflush(stdout);
    err = dhandle.open(&fs,"/");
    if (err<0) printf("Opening directory error\n");
    else printf("OK\n");
    printf("root directory:\n");
    struct dirent *e = dhandle.readdir();             //Get the directory entry
    while (true) {
        err = dhandle.read(e);                        //Read while != 0 (end)  
        if (!err) {
            break;
        }
        printf("    %s\n", e->d_name);
    }
    printf("Closing the root directory... ");
    fflush(stdout);
    err = dhandle.close();
    if (err<0) printf("Closing directory error\n");
    else printf("OK\n");
/******************************************************************************/    

At the very beginning, we are trying to open the testing.txt file for reading / writing. Please note that in addition to the main parameters (file system and file name), we pass the O_RDWR and O_TRUNC flags combined using the “or” bit operation: this is what the rules for working with the OS suggest. The first means that we create or open a file for writing / reading, the second means that the file will be completely overwritten, even if it exists, and is filled with some information. A complete list of flags is provided with the header file " mbed_retarget.h ":
#define O_RDONLY 0      ///< Open for reading
#define O_WRONLY 1      ///< Open for writing
#define O_RDWR   2      ///< Open for reading and writing
#define O_CREAT  0x0200 ///< Create file if it does not exist
#define O_TRUNC  0x0400 ///< Truncate file to zero length
#define O_EXCL   0x0800 ///< Fail if file exists
#define O_APPEND 0x0008 ///< Set file offset to end of file prior to each write
#define O_BINARY 0x8000 ///< Open file in binary mode

In the case of a successful opening (creation) of the file, we write to it what is contained in the block buffer , or rather, the first 10 bytes. The result of the operation will be the number of bytes written during successful recording, and we display this value in the terminal during debugging.
To successfully read data from a file, we need to return to the beginning of the file, and we do this with fhandle.rewind () . We also consider the read bytes, and their number is displayed in the terminal, as well as the file size ( fhandle.size () ).
In the process of debugging, we look into the rblock buffer , into which we read a line from a file. Everything is good:
image

We indulged with the file, now let's see what is in the root directory of the file system. Open the directory and get the number of the entry in the directory: dhandle.readdir () . Now we read the name of all files until dhandle.read (e) returns “0” - there are no more entries in the directory. We display the names of the files, close the directory.
image

We admire the lines in the debugging terminal.
Thanks again to everyone.

Also popular now: