Block Device Drivers for Littlefs
Block Device Drivers for Littlefs

Overview

This library provides implemented drivers for the littlefs file system. Typically, drivers are based on the HAL driver or other high-level middleware such as serial-memory.

General Description

This middleware:

  • Supports two types drivers: SPI NOR flash and SD card (Card mode).
  • Implements the thread safety for multi-threaded RTOS environments using the abstraction-rtos library.
  • Built on top of existing drivers such as serial-memory and HAL
  • Supports Serial Flash Discoverable Parameter (SFDP) mode for SPI flash memories

Quick Start Guide

The Quick Start Guide section has simple examples for the SPI flash and SD Card drivers. Examples are used to initialize the file system on the target memory, demonstrate the creation/opening file, and update its content.

Project creation

  1. Create an empty application using the Project Creator tool in the ModusToolbox™ software.
  2. Add the mtb-littlefs and retarget-io libraries using the Library Manager.
  3. To run an example in the RTOS-environment, add the FreeRTOS library using the Library Manager. Then, add RTOS_AWARE and FREERTOS to the Makefile COMPONENTS variable.
    COMPONENTS += RTOS_AWARE FREERTOS
    Note
    In the RTOS environment, select Active mode for System Idle Power Mode in Power personality (System Tab).
    The next steps are applicable only for the SPI flash driver
  4. To use the same storage for code execution and File System, add the ENABLE_XIP_LITTLEFS_ON_SAME_NOR_FLASH macro to the DEFINES variable in the project Makefile.
    DEFINES += ENABLE_XIP_LITTLEFS_ON_SAME_NOR_FLASH
    Note
    For some devices, the same storage for code execution and File System is used as the default flow. So, adding ENABLE_XIP_LITTLEFS_ON_SAME_NOR_FLASH is mandatory.

HW Configuration in Device Configurator

All HW resources used in this QSG must be configured in the Device Configurator:

  • UART for logging
  • One pin to check the button status
  • SD Card HW - SDHC
  • SPI flash HW - SMIF

If a different alias name is selected, instead of expected resources name in the Device Configurator, the code snippets must be updated.

ResourceName
Quad Serial Memory Interface SMIF_LITTLEFS
SD Host Controller SDHC_LITTLEFS
Serial Communication Block (for debug UART) DEBUG_UART
Pin CYBSP_USER_BTN

Recommended SD Card configuration

Recommended SMIF configuration

Recommended Debug UART

Recommended Button Pin configuration

Add mtb-littlefs logic to main.c

Specific steps for SPI flash driver configuration

  1. Include the required headers.
    #include "lfs_spi_flash_bd.h"
    #include "mtb_serial_memory.h"
    #include "cy_smif.h"
    #include "cycfg_qspi_memslot.h"
  2. If XIP support is enabled, limit the available space for littlefs on the memory device.
    #define FLASH_LFS_ADDRESS_START (0xA00000U)
    #define FLASH_LFS_SIZE (0x600000U)
    Note
    The addresses and sizes depend on the memory device and project memory layout.
  3. Add global variables.
    static mtb_serial_memory_t serial_memory_obj;
    static cy_stc_smif_mem_context_t smifMemContext;
    static cy_stc_smif_mem_info_t smifMemInfo;
  4. Add the function for the NOR HW initialization.
    static void init_lfs(void)
    {
    cy_rslt_t result;
    result = mtb_serial_memory_setup(&serial_memory_obj, MTB_SERIAL_MEMORY_CHIP_SELECT_1, SMIF_LITTLEFS_HW, &SMIF_LITTLEFS_hal_clock,
    &smifMemContext, &smifMemInfo, &smif0BlockConfig);
    if (CY_RSLT_SUCCESS != result)
    {
    check_status("mtb_serial_memory_setup returns error status", (uint32_t)result);
    }
    #if defined (CY_IP_MXS40SSRSS) || defined (CY_IP_MXS22SRSS)
    /* Comment this line if XIP mode is not enabled */
    lfs_spi_flash_bd_configure_memory(&lfs_cfg, FLASH_LFS_ADDRESS_START, FLASH_LFS_SIZE);
    #endif /* #if defined (CY_IP_MXS40SSRSS) */
    result = lfs_spi_flash_bd_create(&lfs_cfg, &serial_memory_obj);
    check_status("Creating SPI flash block device failed.", result);
    }
    cy_rslt_t lfs_spi_flash_bd_create(struct lfs_config *lfs_cfg, mtb_serial_memory_t *serial_memory_obj)
    Initializes the SPI flash and populates the lfs_config structure with the default values.
    Definition: lfs_spi_flash_bd.c:136
    void lfs_spi_flash_bd_configure_memory(const struct lfs_config *lfs_cfg, uint32_t address, uint32_t region_size)
    Configures the memory region used by littlefs.
  5. Add the deinit function.
    static void deinit_lfs(void)
    {
    }
    void lfs_spi_flash_bd_destroy(const struct lfs_config *lfs_cfg)
    De-initializes the SPI flash and frees the resources.
    Definition: lfs_spi_flash_bd.c:226

Specific steps for the SD Card driver configuration

  1. Include the required headers.
    #include "lfs_sd_bd.h"
    #include "mtb_hal_sdhc.h"
    #include "cy_sd_host.h"
  2. Add global variables.
    static mtb_hal_sdhc_t sdhc_obj;
    static cy_stc_sd_host_context_t sdhc_host_context;
  3. Add the interrupt handler for SD Card HW.
    void sd_card_isr(void)
    {
    mtb_hal_sdhc_process_interrupt(&sdhc_obj);
    }
  4. Add the Cy_SD_Host_IsCardConnected() function
    bool Cy_SD_Host_IsCardConnected(SDHC_Type const *base)
    {
    (void) base;
    return true;
    }
  5. Add the function for the SD Card HW initialization.
    static void init_lfs(void)
    {
    cy_rslt_t result;
    cy_en_sd_host_status_t pdl_sdhc_status;
    cy_en_sysint_status_t pdl_sysint_status;
    /* The SD Card should be enabled before calling any other SD Card APIs */
    Cy_SD_Host_Enable(SDHC_LITTLEFS_HW);
    pdl_sdhc_status = Cy_SD_Host_Init(SDHC_LITTLEFS_HW, &SDHC_LITTLEFS_config, &sdhc_host_context);
    if (CY_SD_HOST_SUCCESS != pdl_sdhc_status)
    {
    check_status("Cy_SD_Host_Init returns error status", (uint32_t)pdl_sdhc_status);
    }
    pdl_sdhc_status = Cy_SD_Host_InitCard(SDHC_LITTLEFS_HW, &SDHC_LITTLEFS_card_cfg, &sdhc_host_context);
    if (CY_SD_HOST_SUCCESS != pdl_sdhc_status)
    {
    check_status("Cy_SD_Host_InitCard returns error status", (uint32_t)pdl_sdhc_status);
    }
    result = mtb_hal_sdhc_setup(&sdhc_obj, &SDHC_LITTLEFS_sdhc_hal_config, NULL, &sdhc_host_context);
    if (CY_RSLT_SUCCESS != result)
    {
    check_status("mtb_hal_sdhc_setup returns error status", (uint32_t)pdl_sdhc_status);
    }
    cy_stc_sysint_t sdhc_isr_config =
    {
    .intrSrc = SDHC_LITTLEFS_IRQ,
    .intrPriority = 3U
    };
    pdl_sysint_status = Cy_SysInt_Init(&sdhc_isr_config, sd_card_isr);
    if (CY_SYSINT_SUCCESS != pdl_sysint_status)
    {
    check_status("Cy_SysInt_Init returns error status", (uint32_t)pdl_sdhc_status);
    }
    NVIC_EnableIRQ((IRQn_Type) sdhc_isr_config.intrSrc);
    result = lfs_sd_bd_create(&lfs_cfg, &sdhc_obj);
    check_status("Creating SD Card block device failed.", result);
    }
    cy_rslt_t lfs_sd_bd_create(struct lfs_config *lfs_cfg, const mtb_hal_sdhc_t *sdhc_obj)
    Initializes the SD card interface and populates the lfs_config structure with the default values.
  6. Add the deinit function.
    static void deinit_lfs(void)
    {
    lfs_sd_bd_destroy(&lfs_cfg);
    }
    void lfs_sd_bd_destroy(const struct lfs_config *lfs_cfg)
    De-initializes the SD interface and frees the resources.
    Definition: lfs_sd_bd.c:174

Common code

  1. Include the required headers.
    #include "cybsp.h"
    #include "cy_retarget_io.h"
    #include "lfs.h"
    If the FREERTOS is used, include the next headers.
    #include "FreeRTOS.h"
    #include "task.h"
  2. Add the common define to the project.
    /* Debounce delay for the user button. */
    #define DEBOUNCE_DELAY_MS (50U)
    #if defined(COMPONENT_RTOS_AWARE)
    #define MTB_LFS_TASK_STACK_SIZE (512U)
    #endif /* #if defined(COMPONENT_RTOS_AWARE) */
  3. Add common global variables.
    /* HAL object for button */
    static mtb_hal_gpio_t button_obj;
    static struct lfs_config lfs_cfg;
    #if defined(COMPONENT_RTOS_AWARE)
    static TaskHandle_t mtb_lfs_task_handle;
    #else
    static bool btn_press = false;
    #endif /* #if defined(COMPONENT_RTOS_AWARE) */
  4. Add the function prototypes.
    static void user_button_callback(void *handler_arg, mtb_hal_gpio_event_t event);
    static void btn_init(void);
    static void check_status(const char *message, uint32_t status);
    static void mtb_lfs_boot_count(void * arg);
    static void init_lfs(void);
    static void deinit_lfs(void);
  5. Add the boot count function.
    static void mtb_lfs_boot_count(void * arg)
    {
    /* Avoid compilers error */
    (void) arg;
    uint32_t boot_count = 0U;
    /* Variables used by the filesystem */
    lfs_t lfs_inst;
    lfs_file_t file;
    /* Initialize user button */
    btn_init();
    (void)printf("\nIncrementing the boot count on target memory\n\n");
    /* Initialize the pointers in lfs_cfg to NULL. */
    (void)memset(&lfs_cfg, 0, sizeof(lfs_cfg));
    init_lfs();
    (void)printf("Number of blocks: %"PRIu32"\n", lfs_cfg.block_count);
    (void)printf("Erase block size: %"PRIu32" bytes\n", lfs_cfg.block_size);
    (void)printf("Prog size: %"PRIu32" bytes\n", lfs_cfg.prog_size);
    /* Mount the filesystem */
    int32_t err = lfs_mount(&lfs_inst, &lfs_cfg);
    /* Reformat if we cannot mount the filesystem.
    * This should only happen on the first boot.
    */
    if (err != 0) {
    (void)lfs_format(&lfs_inst, &lfs_cfg);
    (void)lfs_mount(&lfs_inst, &lfs_cfg);
    }
    /* Read the current boot count. */
    (void)lfs_file_open(&lfs_inst, &file, "boot_count", (int32_t)((uint32_t)LFS_O_RDWR | (uint32_t)LFS_O_CREAT)); /* The argument of arithmetic operation must be an unsigned integer but the argument lfs_file_open is defined by a third party and has type signed integer. For MISRA C-2012 */
    (void)lfs_file_read(&lfs_inst, &file, &boot_count, sizeof(boot_count));
    /* Update the boot count. */
    boot_count += 1u;
    (void)lfs_file_rewind(&lfs_inst, &file);
    (void)lfs_file_write(&lfs_inst, &file, &boot_count, sizeof(boot_count));
    /* The storage is not updated until the file is closed successfully. */
    (void)lfs_file_close(&lfs_inst, &file);
    /* Release any resources we were using. */
    (void)lfs_unmount(&lfs_inst);
    /* Print the boot count. */
    (void)printf("\nboot_count: %"PRIu32"\n", boot_count);
    /* Enable the user button interrupt */
    mtb_hal_gpio_enable_event(&button_obj, MTB_HAL_GPIO_IRQ_FALL, true);
    /* Wait until the user button press is notified through the interrupt */
    while (true)
    {
    #if defined (COMPONENT_RTOS_AWARE)
    if(1UL == ulTaskNotifyTake(pdTRUE, portMAX_DELAY))
    {
    #else
    if(btn_press)
    {
    btn_press = false;
    #endif /* #if defined (COMPONENT_RTOS_AWARE) */
    cy_rslt_t result;
    /* Debounce the button press. */
    result = mtb_hal_system_delay_ms(DEBOUNCE_DELAY_MS);
    CY_ASSERT(CY_RSLT_SUCCESS == result);
    (void) result;
    if(!mtb_hal_gpio_read(&button_obj))
    {
    break;
    }
    }
    }
    /* User button is pressed. Format the block device. */
    printf("Formatting the block device...\n\r");
    lfs_format(&lfs_inst, &lfs_cfg);
    printf("Formatting completed...\n\r");
    /* Free the resources associated with the block device. */
    deinit_lfs();
    for (;;)
    {
    }
    }
  6. Add the check status function.
    static void check_status(const char *message, uint32_t status)
    {
    if (0u != status)
    {
    (void)printf("\n================================================================================\n");
    (void)printf("\nFAIL: %s\n", message);
    (void)printf("Error Code: 0x%08"PRIx32"\n", status);
    (void)printf("\n================================================================================\n");
    while(true)
    {
    }
    }
    }
  7. Add the callback function for the user button.
    static void user_button_callback(void *handler_arg, mtb_hal_gpio_event_t event)
    {
    (void) handler_arg;
    (void) event;
    #if defined(COMPONENT_RTOS_AWARE)
    BaseType_t higher_priority_task_woken = pdFALSE;
    vTaskNotifyGiveFromISR(mtb_lfs_task_handle, &higher_priority_task_woken);
    /* Yield if xHigherPriorityTaskWoken was set to true */
    portYIELD_FROM_ISR(higher_priority_task_woken);
    #else
    btn_press = true;
    #endif /* #if defined(COMPONENT_RTOS_AWARE) */
    }
  8. Add the interrupt handler for the user button.
    void btn_isr(void)
    {
    mtb_hal_gpio_process_interrupt(&button_obj);
    }
  9. Add the function for the initialization user button.
    static void btn_init(void)
    {
    cy_en_sysint_status_t pdl_sysint_status;
    /* Initialize the user button used for erasing the block device. */
    mtb_hal_gpio_setup(&button_obj, CYBSP_USER_BTN_PORT_NUM, CYBSP_USER_BTN_PIN);
    mtb_hal_gpio_configure(&button_obj, MTB_HAL_GPIO_DIR_INPUT, MTB_HAL_GPIO_DRIVE_PULLUP);
    mtb_hal_gpio_write(&button_obj, CYBSP_BTN_OFF);
    /* Configure & the user button interrupt */
    mtb_hal_gpio_register_callback(&button_obj, user_button_callback, NULL);
    /* Configure interrupt for button */
    cy_stc_sysint_t btn_isr_config =
    {
    .intrSrc = CYBSP_USER_BTN_IRQ,
    .intrPriority = 3U
    };
    pdl_sysint_status = Cy_SysInt_Init(&btn_isr_config, btn_isr);
    if (CY_SYSINT_SUCCESS != pdl_sysint_status)
    {
    printf("Cy_SysInt_Init returns error status\n\r");
    }
    NVIC_EnableIRQ((IRQn_Type) btn_isr_config.intrSrc);
    printf("Button is configured\n\r");
    }
  10. Update main.c.
    #if defined (COMPONENT_RTOS_AWARE)
    /* Create the user tasks. See the respective task definition for more
    * details of these tasks.
    */
    xTaskCreate(mtb_lfs_boot_count, "LittleFs Task", MTB_LFS_TASK_STACK_SIZE,
    NULL, (configMAX_PRIORITIES - 1), &mtb_lfs_task_handle);
    /* Start the RTOS scheduler. This function should never return */
    vTaskStartScheduler();
    #else
    mtb_lfs_boot_count(NULL);
    #endif /* #if defined (COMPONENT_RTOS_AWARE) */

retarget-io configuration

  1. Add the retarget-io configuration function.
    void retarget_io_config(void)
    {
    cy_rslt_t result;
    static cy_stc_scb_uart_context_t DEBUG_UART_context;
    static mtb_hal_uart_t DEBUG_UART_hal_obj;
    result = (cy_rslt_t)Cy_SCB_UART_Init(DEBUG_UART_HW, &DEBUG_UART_config, &DEBUG_UART_context);
    if (result != CY_RSLT_SUCCESS)
    {
    CY_ASSERT(0);
    }
    Cy_SCB_UART_Enable(DEBUG_UART_HW);
    result = mtb_hal_uart_setup(&DEBUG_UART_hal_obj, &DEBUG_UART_hal_config, &DEBUG_UART_context, NULL);
    if (result != CY_RSLT_SUCCESS)
    {
    CY_ASSERT(0);
    }
    result = cy_retarget_io_init(&DEBUG_UART_hal_obj);
    if (result != CY_RSLT_SUCCESS)
    {
    CY_ASSERT(0);
    }
    printf("Retarget-io is configured\n\r");
    }
  2. Call this function in main.c before any other littlefs functions
    /* Initialize retarget-io to use the debug UART port */
    retarget_io_config();
    printf("*****************************************"
    "mtb-littlefs Filesystem Quick start Guide"
    "***************************************** \n\n");

Project execution

  1. Open a serial terminal. Set the serial port parameters to 8N1 and 115200 baud.
  2. Build and program the project.
  3. If the previous steps were correct, the logs will appear in the serial terminal.
  4. This example initializes the littlefs file system on the target memory device. Create a file with a boot count and a value updated at every button reset is pressed. Also if press the user button, The boot count value must reset to 0.

Configuration Considerations

The driver configuration details are described in the relevant section:

Note
The source files under *<littlefs_path>/bd* are ignored from auto-discovery. Therefore, they will be excluded from compilation because some of the files (e.g., lfs_filebd.c) use POSIX file APIs such as 'open()' and 'close()'. POSIX APIs are not supported by the ModusToolbox™. If you implement POSIX APIs, you can include those files for compilation by adding them to the SOURCES and INCLUDES variables in the Makefile.

Usage in an RTOS environment:

  1. Add FreeRTOS using the Library Manager if FreeRTOS is your choice of RTOS.
  2. If you use a RTOS other than FreeRTOS, add abstraction-rtos using the Library Manager tool. abstraction-rtos is added automatically when you add FreeRTOS using the Library Manager.
  3. Add FREERTOS to the components list when using FreeRTOS.
  4. Add DEFINES=LFS_THREADSAFE to enable the thread-safety for the littlefs APIs.
  5. Add COMPONENTS=RTOS_AWARE in the Makefile to enable RTOS-friendly features, such as waiting on a semaphore until read completion is indicated through an interrupt or a callback.

Dependencies

The dependencies except abstraction-rtos are automatically pulled in when you run the 'make getlibs' command in the ModusToolbox™.

Note
abstraction-rtos is automatically pulled in only when you add FreeRTOS using the Library Manager; otherwise, add it manually.

Changelog

VersionChangesReason for Change
3.0.0 Migrate mtb-littlefs Middleware to the HAL Next flow

More Information

For more information, refer to the links in the README.md