6.5 KiB
LLFS (Linked List File System)
Introduction
The llfs filesystem can be generated using the mkllfs utility. The resulting C file encapsulates the filesystem data within a linked list which can be used by the llfs library.
As a flat filesystem, llfs lacks support for directories. Using the llfs API, information about the files in the filesystem can be retrieved, and the files can be read using direct memory access. Alternatively, the POSIX file functions can be used. But this is more resource intensive, as data must be copied before using it.
It's essential to note that the llfs filesystem operates in a read-only mode, restricting operations solely to read functions.
Table of contents
- Introduction
- Table of contents
- Initialization
- Usage of the llfs API
- Using the POSIX file functions
- Enabling the external loader in STM32CubeIDE
Initialization
Before using the llfs API, or the file related POSIX (fopen, fgetc, ...) functions, the filesystem must be initialized by calling llfs_init().
Usage of the llfs API
The llfs_file_t struct
The llfs_file_t struct contains information about a file in the filesystem.
typedef struct {
const uint8_t* data; // Pointer to the file data (len bytes)
const char* name; // Null-terminated string with the filename
size_t len; // Length of the file data
} llfs_file_t;
The data pointer points to the data of the file in the filesystem, and can be used to read the file.
Getting a list of files
#include "llfs.h"
void main(void) {
llfs_init();
// Allocate space for 10 files
llfs_file_t file_list[10];
// Get the file list
size_t file_count = llfs_file_list(file_list, 10, NULL);
// Loop through the files and print their names and sizes
for (int i = 0; i < file_count; i++) {
LOG_INFO(TAG, "File: %s, size: %d", file_list[i].name, file_list[i].len);
}
}
Result:
[Info] (2009) [main]: File: image.bmp, size: 9270
[Info] (2013) [main]: File: python_file.py, size: 645
[Info] (2019) [main]: File: image2.bmp, size: 7738
[Info] (2024) [main]: File: filename with a space.txt, size: 61
[Info] (2031) [main]: File: file1.txt, size: 77
It is also possible to use a file extension filter (e.g. *.bmp, *.txt, *.py).
// ...
size_t file_count = llfs_file_list(file_list, 10, "*.bmp");
// ...
This will only return files with the .bmp extension.
[Info] (2009) [main]: File: image.bmp, size: 9270
[Info] (2019) [main]: File: image2.bmp, size: 7738
Iterator function (not recommended)
It is also possible to iterate through the files without allocating an array.
When the memory pointer is NULL, the iterator will start at the beginning of the file list.
Each call to llfs_next_file() will return the next file in the list,
if a filter is specified files that don't match the filter will be skipped.
#include "llfs.h"
void main(void) {
llfs_init();
// Get the file list
void* mem = NULL; // Pointer for internal use by the llfs library
llfs_file_t* file;
while ((file = llfs_next_file(&mem, ".bmp")) != NULL) {
LOG_INFO(TAG, "File: %s", file->name);
}
}
While this method doesn't require allocating memory for the file list, it is slower than the previous method due to the overhead calling the function for each file. Additionally, the required memory for the filelist is very small, so it's recommended to use the first method.
Reading a file
#include "llfs.h"
void main(void) {
llfs_init();
// Get a file by name
llfs_file_t* file = llfs_file_open("filename with a space.txt");
if (file != NULL) {
// Print the file name, size and data
LOG_INFO(TAG, "File found: %s, size: %d", file->name, file->len);
LOG_INFO(TAG, "File data: %s", file->data);
} else {
LOG_WARN(TAG, "File not found");
}
}
Result:
[Info] (2040) [main]: File found: filename with a space.txt, size: 61
[Info] (2047) [main]: File data: This is a file with a space in it's filename.
Getting the number of files
#include "llfs.h"
void main(void) {
llfs_init();
// Get the number of files
size_t file_count = llfs_file_count();
// Print the number of files
LOG_INFO(TAG, "File count: %d", file_count);
}
Getting a file extension
#include "llfs.h"
void main(void) {
llfs_init();
// Get a file by name
llfs_file_t* file = llfs_file_open("filename with a space.txt");
if (file != NULL) {
// Print the file name, size and data
LOG_INFO(TAG, "File found: %s, size: %d", file->name, file->len);
LOG_INFO(TAG, "File data: %s", file->data);
// Get the file extension
const char* ext = llfs_file_ext(file->name);
LOG_INFO(TAG, "File extension: %s", ext);
} else {
LOG_WARN(TAG, "File not found");
}
}
Using the POSIX file functions
The llfs library also supports the POSIX file functions.
As the file system is read-only, write functions are not implemented.
There is also a limit on the number of files that can be open concurrently,
this is set by the POSIX_MAX_FILES macro in llfs.c.
The default value is 10, but there are already 3 files in use (stdin, stdout, stderr),
so the maximum number of files that can be open is 7.
The following functions are tested and working, but other functions might also work:
fopenfclosefgetcfreadfseekftellrewindfstatfileno
Enabling the external loader in STM32CubeIDE
In order to write the file system data to the QSPI flash, the external loader must be enabled in STM32CubeIDE. This can be done by opening the debug configuration:
Then, in the Debugger tab:
3. Enable the External Loader
4. Click the Scan button
5. Select the correct loader: N25Q128A_STM32F746G-DISCO, 0x90000000 ...

