No-OS, Non-Blocking LittleFS

In another post I mentioned that I switched from FATFS to LittleFS (LFS) on an STM32. This caused me to write a file explorer for LittleFS in Windows.

https://bluscape.blog/2019/10/01/littlefs-explorer-lfse-for-windows/

Prior to switching to LittlFS, I was in the process of converting a FATFS library to a No Operating System (No-OS), Non-blocking library using Protothreads. I also started a post on this and might still complete it.

The reason why I converted the FATFS driver to No-OS, Non-blocking was that I’m not willing to block the program execution while the file system (FS) is waiting for disk reads and writes to complete.

LFS had the same problem. There is no feedback mechanism to allow the program to resume while the disk access is in progress. But the problem is not only with the FS drivers, the disk IO drivers provided by ST are blocking too. If you have a look for example at the ST SDIO SD card driver, in DMA mode, the DMA is interrupt driven but the driver is blocking and polling for results in a while loop.

Examples of the blocking ST SDIO SD driver

// From stm324x7i_eval_sdio_sd.c
SD_Error SD_WaitReadOperation(void)
{
  ...
  
  // Wait for the transfer to complete (blocking and polling)
  while ((DMAEndOfTransfer == 0x00)&&(TransferEnd == 0)&&
  (TransferError == SD_OK) && (timeout > 0))
  {
      timeout--;
  }

  ...

  // Wait for the read to become inactive (blocking and polling)
  while(((SDIO->STA & SDIO_FLAG_RXACT)) && (timeout > 0))
  {
      timeout--;  
  }

  ...
}

// From fatfs_drv.c
DRESULT disk_read(BYTE drv, BYTE *buff, DWORD sector, BYTE count)
{
  ...

  // Wait for the read operation to complete (blocking and polling)
  sdstatus =  SD_WaitReadOperation();

  // Wait until the transfer is OK (blocking and polling)
  while(SD_GetStatus() != SD_TRANSFER_OK);

 ...
}

There are several ways to overcome this and one of them is to use an OS with context switching, but I’m not a fan of context switching. This again consumes precious RAM resources and a couple other reasons too.

It was a massive task to convert FATFS to non-blocking and then I still had to do testing. It felt like I’m investing too much time into something of which I’m not certain about the results, and had no idea how much is still involved to get it to the point where it is stable without having to get to grips with the driver code and having a reasonable understanding of the FATFS specification. I started looking at alternative file systems for embedded implementation. Something simpler and more modern. I had a look several file systems and then came across LFS.

You can find a list of file systems here: https://en.wikipedia.org/wiki/List_of_file_systems

Just to give you an idea, I counted the lines of code using LocMetrics (http://www.locmetrics.com/) searching only in C source and header files. With the reduced line count it is much easier to find problems.

FATFS R0.14LFS 2.1
Lines of code23648 lines6138 lines

LFS seemed modern (more modern than FATFS), robust and the code was also much less than that of FATFS. The power-loss resilience and dynamic wear leveling of LFS was also very attractive.

So I converted the standard LFS library to non-blocking library using Protothreads. But this required that I convert the SDIO SD driver to non-blocking too.

The numbers: Standard LFS vs. Non-Blocking LFS

To compare the “performance” between the standard and a non-blocking LFS libraries, I wrote two test routines that continuously reads a 10k (10240 bytes) file from an SD card using LFS. Each read operation will read a 512 byte block from the SD card. The first routine uses the standard version of the LFS library whereas the second routine uses the converted, non-blocking, version of the LFS library. Both routines mount the FS, open a file, read to the end of the file and then rewind the file. The read and rewind operations are repeated. I’m changing the state of an IO pin to measure the the timing of the following items:

  • Time per read iteration (Non-blocking version only).
  • Time to read a 512 byte block.
  • Time to read the 10k file.

The position, in code, where the IO pin is changed was moved to allow the measurement of the respective items. There is a 10ms delay between each file read. The IO pin is set high during read operations and set low once the read operation is completed.

Following is the LFS configuration and both the Standard and Non-Blocking LFS test routines.

LFS Configuration (for both routines)

const struct lfs_config LFSConfig_S =
{
  .read = DISK_Read_S,
  .prog = DISK_Write_S,
  .erase = DISK_Erase_S,
  .sync = DISK_Sync_S,
  .read_size = 512,
  .prog_size = 512,
  .block_size = 512,
  .block_count = 7580712,
  .block_cycles = 1000,
  .cache_size = 512,
  .lookahead_size = 512,
  .name_max = 255,
  .file_max = 2147483647,
  .attr_max = 1022,
  .read_buffer = connectivity_fs_ReadBuffer,
  .prog_buffer = connectivity_fs_ProgBuffer,
  .lookahead_buffer = connectivity_fs_LookAheadBuffer,
};

Standard LFS Test Routine

static PT_THREAD(TestThread_V(struct pt* Thread_PS))
{
#define LFS_EOF 0
#define IO_PIN_BLOCK
//#define IO_PIN_FILE
  static enum lfs_error FileResult_E;
  static lfs_ssize_t BytesRead_S;
  static uint8_t ReadBuffer_AU8[512];
  static lfs_file_t LFSFileHandle_S;
  static lfs_t LFSHandle_S;
  static struct pt LocalThread_S;
  static TTIMER_S LocalTimer_S;

  // Begin the thread
  PT_BEGIN(Thread_PS);

  // Try and mount the disk
  FileResult_E = lfs_mount(&LFSHandle_S, &LFSConfig_S);

  // If there was an error
  if (FileResult_E < LFS_ERR_OK)
  {
    // Exit the thread
    PT_EXIT(Thread_PS);
  }

  // Try and open the file
  FileResult_E = lfs_file_open(&LFSHandle_S, &LFSFileHandle_S, 
                               "TestFile.jpg", LFS_O_RDONLY);

  // If there was an error
  if (FileResult_E < LFS_ERR_OK)
  {
    // Exit the thread
    PT_EXIT(Thread_PS);
  }

  // Loop forever
  while (1)
  {
    // Set the pin (for oscilloscope measurement)
    GPIO_SetBits(Measurement_Port, Measurement_Pin);

    // Try and read from the file
    BytesRead_S = lfs_file_read(&LFSHandle_S, &LFSFileHandle_S, 
                                ReadBuffer_AU8, 512);

#ifdef IO_PIN_BLOCK
    // Clear the pin (for oscilloscope measurement)
    GPIO_ResetBits(Measurement_Port, Measurement_Pin);
#endif

    // If there was an error
    if (BytesRead_S < LFS_ERR_OK)
    {
      // Exit the thread
      PT_EXIT(Thread_PS);
    }

    // If we reached the end of the file
    if (BytesRead_S == LFS_EOF)
    {

#ifdef IO_PIN_FILE
    // Clear the pin (for oscilloscope measurement)
    GPIO_ResetBits(Measurement_Port, Measurement_Pin);
#endif

      // Try and rewind the file
      FileResult_E = lfs_file_rewind(&LFSHandle_S, &LFSFileHandle_S);

      // If there was an error
      if (FileResult_E < LFS_ERR_OK)
      {
        // Exit the thread
        PT_EXIT(Thread_PS);
      }

      // Wait a little
      TIMER_Start_V(&LocalTimer_S, TIMER_10_MS);
      PT_WAIT_UNTIL(Thread_PS, TIMER_Expired_B(&LocalTimer_S));
    }
  }

  // End the thread
  PT_END(Thread_PS);
}

Non-Blocking LFS Test Routine

static PT_THREAD(TestThread_V(struct pt* Thread_PS))
{
#define LFS_EOF 0
#define IO_PIN_ITERATION
//#define IO_PIN_BLOCK
//#define IO_PIN_FILE
  static enum lfs_error FileResult_E;
  static lfs_ssize_t BytesRead_S;
  static uint8_t ReadBuffer_AU8[512];
  static lfs_file_t LFSFileHandle_S;
  static lfs_t LFSHandle_S;
  static struct pt LocalThread_S;
  TPT_Results_E ThreadResult_E;
  static TTIMER_S LocalTimer_S;

  // Begin the thread
  PT_BEGIN(Thread_PS);

  // Lock the mutex
  PT_SEM_WAIT(Thread_PS, &LFS_Mutex_S);

  // Try and mount the disk
  PT_SPAWN(Thread_PS, &LocalThread_S, 
           lfs_mount(&LocalThread_S, &LFSHandle_S, &LFSConfig_S, 
                     (int*)&FileResult_E));

  // Unlock the mutex
  PT_SEM_SIGNAL(Thread_PS, &LFS_Mutex_S);

  // If there was an error
  if (FileResult_E < LFS_ERR_OK)
  {
    // Exit the thread
    PT_EXIT(Thread_PS);
  }

  // Lock the mutex
  PT_SEM_WAIT(Thread_PS, &LFS_Mutex_S);

  // Try and open the file
  PT_SPAWN(Thread_PS, &LocalThread_S, 
           lfs_file_open(&LocalThread_S, &LFSHandle_S, &LFSFileHandle_S, 
                         "TestFile.jpg", LFS_O_RDONLY, 
                         (int*)&FileResult_E));

  // Unlock the mutex
  PT_SEM_SIGNAL(Thread_PS, &LFS_Mutex_S);

  // If there was an error
  if (FileResult_E < LFS_ERR_OK)
  {
    // Exit the thread
    PT_EXIT(Thread_PS);
  }

  // Loop forever
  while (1)
  {
    // Lock the mutex
    PT_SEM_WAIT(Thread_PS, &LFS_Mutex_S);

    // Initialise the thread
    PT_INIT(&LocalThread_S);

    // Loop until block read is complete
    while (1)
    {
      // Set the pin (for oscilloscope measurement)
      GPIO_SetBits(Measurement_Port, Measurement_Pin);

      // Try and read from the file
      ThreadResult_E = lfs_file_read(&LocalThread_S, &LFSHandle_S, 
                                     &LFSFileHandle_S, ReadBuffer_AU8, 
                                     512, &BytesRead_S);

#ifdef IO_PIN_ITERATION
      // Clear the pin (for oscilloscope measurement)
      GPIO_ResetBits(Measurement_Port, Measurement_Pin);
#endif

      // If the thread completed
      if (ThreadResult_E >= PT_EXITED)
      {
        break;
      }
    }

#ifdef IO_PIN_BLOCK
      // Clear the pin (for oscilloscope measurement)
      GPIO_ResetBits(Measurement_Port, Measurement_Pin);
#endif

    // Unlock the mutex
    PT_SEM_SIGNAL(Thread_PS, &LFS_Mutex_S);

    // If there was an error
    if (BytesRead_S < LFS_ERR_OK)
    {
      // Exit the thread
      PT_EXIT(Thread_PS);
    }

    // If we reached the end of the file
    if (BytesRead_S == LFS_EOF)
    {

#ifdef IO_PIN_FILE
    // Clear the pin (for oscilloscope measurement)
    GPIO_ResetBits(Measurement_Port, Measurement_Pin);
#endif

      // Lock the mutex
      PT_SEM_WAIT(Thread_PS, &LFS_Mutex_S);

      // Try and rewind the file
      PT_SPAWN(Thread_PS, &LocalThread_S, 
               lfs_file_rewind(&LocalThread_S, 
                               &LFSHandle_S, &LFSFileHandle_S, 
                               (int*)&FileResult_E));

      // Unlock the mutex
      PT_SEM_SIGNAL(Thread_PS, &LFS_Mutex_S);

      // If there was an error
      if (FileResult_E < LFS_ERR_OK)
      {
        // Exit the thread
        PT_EXIT(Thread_PS);
      }

      // Wait a little
      TIMER_Start_V(&LocalTimer_S, TIMER_10_MS);
      PT_WAIT_UNTIL(Thread_PS, TIMER_Expired_B(&LocalTimer_S));
    }
  }

  // End the thread
  PT_END(Thread_PS);
}

Results

The time for each item was measured on a IO pin using an oscilloscope. The standard library does not have a time per iteration since it will block until the read is complete. The time to read a block varied slightly, with a couple microseconds, over several measurements and the values for the block reads noted in the table below are only from the oscilloscope measurements in this post. But in general, the time to read a block between the standard and non-blocking routines was the same. The non-blocking version took slightly longer (less than 1ms longer) to read the entire file.

Standard LFSNon-Blocking LFS
Time per iterationN.A.3.463us
Number of iterationsN.A.101 (min)
1398 (max)
377 (avg)
Time to read a 512 byte block591.8us (min)
6.29ms (max)
591.8us (min)
6.34ms (max)
Time to read the 10k file42.64ms43.43ms

But what is really of interest here is the time per iteration. If you are like me and do not want to use an OS with context switching, you can reduce your read time (per iteration) from roughly speaking 591us (worst case 6.26ms) down to 3.5us. A saving of 588.33us (worst case 6.25ms). That is a hell of a lot of processing time. Valuable processing time that I can spend doing other things. So instead of blocking the program execution for 591us (worst case 6.26ms), we are now blocking it for only 3.5us.

Oscilloscope measurements

Standard LFS

Standard: Minimum read time per block (591.8us).
Standard: Maximum read time per block (6.29ms).
Standard: Total block reads for a 10k file (20 x 512 byte blocks).
Standard: Total read time for a 10k file (42.64ms).

Non-blocking LFS

Non-Blocking: Typical read time per iteration (3.463uS).
Non-Blocking: Total iterations for a 512 byte block read.
Non-Blocking: Minimum read time per block (591.8us).
Non-Blocking: Maximum read time per block (6.348ms).
Non-Blocking: Total block reads for a 10k file (20 x 512 byte blocks).
Non-Blocking: Total read time for a 10k file (43.43ms).

Notes on the Non-Blocking LFS Test Routine

The non-blocking routine was slightly modified to allow measurement between read iterations but will produce the same time per iteration if it was implemented in a normal manner. The only difference between the modified and normal non-blocking routines are the spawning of the child thread using “PT_SPAWN” when reading the file instead of manually checking the thread result. The normal implementation of the non-blocking routine will look like this:

Non-Blocking LFS Routine (normal implementation)

static PT_THREAD(TestThread_V(struct pt* Thread_PS))
{
#define LFS_EOF 0
  static enum lfs_error FileResult_E;
  static lfs_ssize_t BytesRead_S;
  static uint8_t ReadBuffer_AU8[512];
  static lfs_file_t LFSFileHandle_S;
  static lfs_t LFSHandle_S;
  static struct pt LocalThread_S;
  TPT_Results_E ThreadResult_E;
  static TTIMER_S LocalTimer_S;

  // Begin the thread
  PT_BEGIN(Thread_PS);

  // Lock the mutex
  PT_SEM_WAIT(Thread_PS, &LFS_Mutex_S);

  // Try and mount the disk
  PT_SPAWN(Thread_PS, &LocalThread_S, 
           lfs_mount(&LocalThread_S, &LFSHandle_S, &LFSConfig_S,  
                     (int*)&FileResult_E));

  // Unlock the mutex
  PT_SEM_SIGNAL(Thread_PS, &LFS_Mutex_S);

  // If there was an error
  if (FileResult_E < LFS_ERR_OK)
  {
    // Exit the thread
    PT_EXIT(Thread_PS);
  }

  // Lock the mutex
  PT_SEM_WAIT(Thread_PS, &LFS_Mutex_S);

  // Try and open the file
  PT_SPAWN(Thread_PS, &LocalThread_S, 
           lfs_file_open(&LocalThread_S, &LFSHandle_S, &LFSFileHandle_S, 
                         "TestFile.jpg", LFS_O_RDONLY, 
                         (int*)&FileResult_E));

  // Unlock the mutex
  PT_SEM_SIGNAL(Thread_PS, &LFS_Mutex_S);

  // If there was an error
  if (FileResult_E < LFS_ERR_OK)
  {
    // Exit the thread
    PT_EXIT(Thread_PS);
  }

  // Loop forever
  while (1)
  {
    // Lock the mutex
    PT_SEM_WAIT(Thread_PS, &LFS_Mutex_S);

    // Set the pin (for oscilloscope measurement)
    GPIO_SetBits(LED6_RED_GPIO_Port, LED6_RED_Pin);

    // Try and read from the file
    PT_SPAWN(Thread_PS, &LocalThread_S, 
             lfs_file_read(&LocalThread_S, &LFSHandle_S, &LFSFileHandle_S, 
                           ReadBuffer_AU8, 512, &BytesRead_S));

    // Clear the pin (for oscilloscope measurement)
    GPIO_ResetBits(LED6_RED_GPIO_Port, LED6_RED_Pin);

    // Unlock the mutex
    PT_SEM_SIGNAL(Thread_PS, &LFS_Mutex_S);

    // If there was an error
    if (BytesRead_S < LFS_ERR_OK)
    {
      // Exit the thread
      PT_EXIT(Thread_PS);
    }

    // If we reached the end of the file
    if (BytesRead_S == LFS_EOF)
    {
      // Lock the mutex
      PT_SEM_WAIT(Thread_PS, &LFS_Mutex_S);

      // Try and rewind the file
      PT_SPAWN(Thread_PS, &LocalThread_S, 
               lfs_file_rewind(&LocalThread_S, &LFSHandle_S, 
                               &LFSFileHandle_S, (int*)&FileResult_E));

      // Unlock the mutex
      PT_SEM_SIGNAL(Thread_PS, &LFS_Mutex_S);

      // If there was an error
      if (FileResult_E < LFS_ERR_OK)
      {
        // Exit the thread
        PT_EXIT(Thread_PS);
      }

      // Wait a little
      TIMER_Start_V(&LocalTimer_S, TIMER_10_MS);
      PT_WAIT_UNTIL(Thread_PS, TIMER_Expired_B(&LocalTimer_S));
    }
  }

  // End the thread
  PT_END(Thread_PS);
 }

Memory Usage

Both test routines were compiled with the following parameters:

  • IDE: STM32CubeIDE v1.1.0
  • Compiler: gcc version 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907] (GNU Tools for STM32 7-2018-q2-update.20190328-1800)
  • Compiler Settings: -mcpu=cortex-m4 -std=gnu11 -g3 -c -Os -ffunction-sections -fdata-sections -Wall -fstack-usage –specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb
  • Linker Settings: -mcpu=cortex-m4 – –specs=nosys.specs -Wl,-Map=”${ProjName}.map” -Wl,–gc-sections -static –specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -Wl,–start-group -lc -lm -Wl,–end-group

I extracted the memory usage (FLASH and RAM) from the MAP and ELF files using a tool called MapViewer ( https://github.com/govind-mukundan/MapViewer ). The memory usage below is for the FS driver only.

Standard LFSNon-Blocking LFSDifference
FLASH (text)11776 bytes21940 bytes10164 bytes
RAM (data and bss)1769 bytes3357 bytes1588 bytes

Conclusion

  • Was this exercise worthwhile? For my application yes. I need the processor bandwidth to do other things while accessing the disk through a file system.
  • Is it worth the cost of extra memory usage? I do not like using more memory than necessary and the additional memory usage is a bit higher than what I would have liked but I can always use a slightly larger device if needed . At the gain of processor bandwidth I would say yes.
  • I would like to see what is the memory implications when using the standard LFS library with an OS, but for that reason only. So I might do a test at some point and amend this post.

Let me know me what you think.

LittleFS Explorer (LFSE) for Windows

I recently switched from FATFS to LittleFS (LFS) on an STM32 . LittleFS is a fail-safe file system designed for microcontrollers:

https://github.com/ARMmbed/littlefs

The only problem I had with LFS is that I could not edit the file system in a Windows environment because it is not recognized by Windows. There is a FUSE driver for Linux, but I’m developing under Windows, so I needed an interface from where I could format the SD card to LFS, browse, edit, add and remove files. So I decided to write a file explorer for LFS in Windows.

Download: https://github.com/bluscape/LittleFS-Windows-Explorer

Video: https://youtu.be/4D5XyWgXUhY

Features

  • Automatically detects, mount and unmount LFS formatted disks.
  • Mount binary files (See Mounting binary files section).
  • Format any accessible disk to LFS.
  • Delete a volume (Deletes the table of contents).
  • Automatically detects the operating system disk and prevents formatting the operating system disk (Requires elevated rights. See Disk List description).
  • Define custom LFS configuration parameters when mounting or formatting a disk.
  • Browse folders.
  • Drag and drop, single or multiple, files and folders from Windows explorer to LFSE.
  • Create new folders.
  • Rename and delete files and folders.
  • View and edit files with the default associated Windows program.
  • View and edit files with a specific Windows program.
  • Grid view or detail view (file size).

Pending features

Following is a list of features that I still want to implement:

  • Migrate from a previous version of LFS (LFS_MIGRATE).
  • Drag and drop, single or multiple, files and folders from LFSE to Windows explorer.
  • Folder and file search.
  • Cut, copy and paste folders and files inside LFSE.

User guide

1. Toolbar

The toolbar has 4 buttons:

  • Refresh – This will refresh the disk list. If an LFS formatted disk is selected, it will refresh the contents of the current directory of the LFS formatted disk.
  • New folder – It will create a new folder in the current directory of the LFS formatted disk. This button is enabled when an LFS formatted disk is selected.
  • Delete – It will delete the selected item from the LFS formatted disk. This cannot be undone. This button is enabled when an item is selected from an LFS formatted disk. You cannot delete the current “.” or parent “..” directories.
  • Detail view/Icon view – Switch between detail and icon view for the disk contents. This button is enabled when an LFS formatted disk is selected.

2. Disk list

The disk list will contain all the detected logical disks.

This icon indicates that the disk is accessible and LFS formatted. Click on this icon to display the disk contents. Right click on this icon to display its popup menu.

This icon indicates that the disk is either not accessible or not LFS formatted. Right click on this icon to display its popup menu.

undefinedThis icon indicates that the disk is the operating system disk and cannot be formatted to LFS. LFSE needs elevated rights (administrator) to detect the operating system disk.

The Format dialog

The format dialog makes provision for all the LFS configuration parameters. It will display the detected disk size and automatically calculate the block count based on the disk size and block size. You can also save the current configuration parameters as the default, which will be loaded the next you you format a disk.

3. Disk contents

The contents of the selected LFS formatted disk will be displayed here.

Double click on a folder to view it’s contents. Double click on the current (“.”) or parent (“..”) folder to browse to the current or parent folder. Right click on this icon to display its popup menu.

Double click on a file to open it with the default Windows program. LFS is not recognised by Windows and therefore it cannot access the file directly from the LFS formatted drive. A temporary file, with the same name, is created in the application directory (temp) from where it is launched in Windows. LFSE will automatically update any changes to the temporary file to the original file. Right click on this icon to display its popup menu.

Shortcut keys

The shortcut keys apply to the contents area only:

  • Ctrl+A = Select all
  • Shift+Del = Delete selected
  • Del = Delete selected

Mounting binary files

You will need a tool that is capable of mounting a binary file as a virtual logical drive. I’ve tested it with OSFMount:

Download: https://www.osforensics.com/tools/mount-disk-images.html

Steps:

  • Download and install OSFMount.
  • Go to “File”->”Mount new virtual disk…” and select your binary file containing the LFS data and click “Next”.
  • Select “Mount entire image as virtual disk” and click “Next”.
  • Uncheck the “Read-only drive” checkbox and set “Write mode” to “Direct” if you would like to write to the FS.
  • Set the “Drive emulation” to “Logical Drive Emulation”, set the “Drive type” to “HDD” and set the “Drive letter” to “Auto”.
  • Click “Mount”.
  • Your binary should now be mounted as a logical drive.
  • Open LFSE (you might need to run LFSE as administrator) and proceed as normal.

I will add the option to load binary files directly from LFSE at a later stage.

Debugging

LFS supports the following debug information:

  • Trace
  • Debug
  • Warn
  • Error

A log file is created, inside the application directory, for each debug item. The log files are overwritten whenever an LFS formatted disk is selected (mounted). In addition to the LFS debug information, LFSE creates its own log file, also inside the application directory, with warnings and errors. The LFSE log file is overwritten every time the application is started.

Implementation

I initially wrote the LittleFS Explorer (LFSE) in Qt. But I am more familiar with Delphi VCL controls, and although the Qt version is fully functional, I decided to switch to Delphi for further development.

Now you might be asking: “Delphi?”

Well, Delphi Community Edition is free (so is QT and C# community edition and…) and once again I am more familiar with Delphi VCL controls. Especially in an application like this where I need to take control of visual controls and override the enforced Windows manifest. I’m proficient in C# but would not want to implement low level disk access in C# with all its managed memory restrictions.

For the Delphi version, I compiled the LittleFS C source into a C DLL using CodeBlocks and MinGW and called the DLL functions from the Delphi application. I’ve added a couple of my own functions to the DLL to assist in the implementation. I’ve also basically completed a File System Driver (FSD) for LittleFS in Windows. If time allows, I will upload the Qt version, FSD and sources.

The Qt version of LittleFS Explorer.

Version info and testing

The revision history of LFSE and its DLL can be found on the GitHub repository.

The version of the LFS source used in the DLL can be seen in the about dialog.

LFSE and its DLL was compiled for 32-bit platforms only, but can be used on a 64-bit platform.

I’ve tested LFSE with an SD card on a 64-bit platform running Windows 10 Professional. I will appreciate your feedback on other platforms and Windows operating systems.

Warning: variable ‘PT_YIELD_FLAG’ set but not used

When writing software, I prefer to enable all warnings and errors and also treat warnings as errors. This way I know I’ve covered my bases in terms of compiler detected errors and warnings, and when resolved, my compiler output window is clean.

Compiling software that uses Protothreads, you sometimes get: “warning: variable ‘PT_YIELD_FLAG’ set but not used”.

If you have a look at what is happening in the background with Protothreads, you will see this:

#define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc)

#define PT_YIELD(pt)				\
	do {					\
		PT_YIELD_FLAG = 0;		\
		LC_SET((pt)->lc);		\
		if(PT_YIELD_FLAG == 0) {	\
			return PT_YIELDED;	\
		}				\
	} while(0)

#define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \
                   PT_INIT(pt); return PT_ENDED; }

A variable “PT_YIELD_FLAG” is declared and assigned a value of one for every call to “PT_BEGIN” . With “PT_END”, the variable is assigned a value of zero. Other than that, “PT_YIELD_FLAG” is never used unless you call “PT_YIELD”. So if you do not use or have the need for “PT_YIELD” in your software, you will get this warning.

To eliminate this warning you can either suppress it from your compiler settings (With GCC you can use the -W option) or handle it in software by declaring “PT_YIELD_FLAG” as unused with the “unused” attribute. I prefer to handle it in software.

#define PT_BEGIN(pt) { char __attribute__ ((unused)) PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc)

This attribute will tell the compiler that the variable “PT_YIELD_FLAG” is possibly unused and will not generate a compiler warning when not in use.

For more info on the GCC -W option and compiler attributes:

https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Warning-Options.html#Warning-

and

https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Common-Type-Attributes.html#Common-Type-Attributes