Skip to content

File System

Introduction

The File System module in NECTO IDE provides a seamless way to integrate file storage and retrieval capabilities into embedded systems, with a primary focus on SD card support utilizing the FAT file system. This module simplifies the often complex task of managing file operations in embedded environments, allowing developers to work with files in a manner similar to desktop systems, but optimized for the limited resources typical of microcontroller platforms.

This comprehensive guide serves as a reference for developers, outlining the key features and functionalities of the MikroSDK.FileSystem API. It aims to provide clear, practical insights into how this API can be effectively used to implement robust and efficient file storage solutions on microcontroller-based systems. Whether you are working with large amounts of data or simply need reliable file management on an SD card, this guide will help you navigate the capabilities of the MikroSDK.FileSystem module to meet the storage requirements of your embedded application.

API Name

  • MikroSDK.FileSystem

API Files

Prerequisites

  • Library Manager:

    • Select FileSystem, PhysicalDrive and Board to add the File System to your project using the Library Manager in NECTO Studio.
  • Headers:

    • Include the MikroSDK.FileSystem, MikroSDK.PhysicalDrive, MikroSDK.Board headers in your source files to access File System functions.
    • This set of headers pop up in the lower right part of NECTO Studio once you perform the previous task (while selecting FileSystem, and Board in Library Manager)
    • Alternatively, include the fatfs.h, file.h, dir.h, sdspi_physical_drive.h, and ff_types.h headers in your source files to access the FileSystem functions.
  • CMakeLists:

    • You do not have to perform anything, configuration (CMake) file is already taken care of automatically while you performed the first task in this Prerequisites!
    • You can ensure that your CMakeLists.txt includes the necessary configurations to link against the FileSystem library.

Code Examples

  • SD Card File Manipulation with NECTO IDE and mikroSDK v2.0 Framework
/* Project name:
 *   SD Card File Manipulation with NECTO IDE and mikroSDK v2.0 Framework
 * Copyright:
 *   (c) MIKROE, 2024.
 * Description:
 *   - This example demonstrates how to format, create, write to, read from,
 *     and manipulate files on an SD card using the FAT file system in NECTO Studio.
 *   - The code opens a file on the SD card, writes data to it, and performs
 *     a file seek operation to verify correct file handling.
 *   - The test outcome is indicated via digital output pins to show success or failure.
 * Library dependencies:
 *   - Ensure `FileSystem`, `PhysicalDrive`, `Log`, and `Board` libraries are enabled
 *     in NECTO Studio's Library Manager to ensure successful compilation and functionality.
 * How to test this code example:
 *   - 1. Insert [mikroSD Click]https://www.mikroe.com/microsd-click with SD card
 *        into the appropriate socket on one of the
 *        [development boards](https://www.mikroe.com/development-boards-v8)
 *        (this example is set to utilize mikroBUS 1 socket).
 *   - 2. Ensure the board is powered and connected to the PC.
 *   - 3. Compile and flash the code to the MCU.
 *   - 4. Use the USB UART to monitor logs during execution (alternatively,
 *        utilize Debug Mode and `Application Output` for monitoring).
 *   - 5. Observe the status LEDs.
 */

// ------------------------------------------------------------------ INCLUDES
/**
 * Any initialization code needed for MCU to function properly.
 * Do not remove this line or clock might not be set correctly.
 */
#ifdef PREINIT_SUPPORTED
#include "preinit.h"
#endif

#include "fatfs.h"                  // File system handling library
#include "file.h"                   // File operations library
#include "board.h"                  // Board-specific definitions and configurations
#include "sdspi_physical_drive.h"   // SD card handling via SPI
#include "ff_types.h"               // FAT filesystem types
#include "log.h"                    // Logging utility
#include "delays.h"                 // Delay functions for timing operations

// -------------------------------------------------------------------- MACROS
#define SEEK_TEST_LENGTH       (5)   // Length of the buffer used for seek test
#define BUFFER_SIZE            (512) // Buffer size for file read/write operations

// Define pins to indicate test status through LEDs
#define TEST_SUCCESS_PIN NC // TODO: use pin PD12 for example
#define TEST_FAILED_PIN NC // TODO: use pin PD13 for example

// ----------------------------------------------------------------- VARIABLES
digital_out_t status;                // Status LED control

typedef enum {
    TEST_FS_NO_ERROR = 0,            // No error
    TEST_FS_ERROR_MKDIR,             // Error in directory creation
    TEST_FS_ERROR_REMOVE,            // Error in file/directory removal
    TEST_FS_ERROR_RENAME,            // Error in renaming file/directory
    TEST_FS_ERROR_FORMAT,            // Error in drive formatting
    TEST_FS_ERROR_MOUNT,             // Error in mounting the file system
    TEST_FS_ERROR_FOPEN,             // Error in opening a file
    TEST_FS_ERROR_FCLOSE,            // Error in closing a file
    TEST_FS_ERROR_FREED,             // Error in reading from a file
    TEST_FS_ERROR_FWRITE,            // Error in writing to a file
    TEST_FS_ERROR_FSYNC,             // Error in syncing file data
    TEST_FS_ERROR_FSEEK,             // Error in seeking file position
    TEST_FS_ERROR_FTELL,             // Error in getting file position
    TEST_FS_ERROR_FREWIND,           // Error in rewinding file
    TEST_FS_ERROR_FSIZE,             // Error in getting file size
    TEST_FS_ERROR_FTRUNCATE,         // Error in truncating file size
    TEST_FS_ERROR_DOPEN,             // Error in opening directory
    TEST_FS_ERROR_DCLOSE,            // Error in closing directory
    TEST_FS_ERROR_DREAD,             // Error in reading directory contents
    TEST_FS_ERROR_DREWIND,           // Error in rewinding directory
    TEST_FS_ERROR_DSIZE              // Error in getting directory size
} test_fs_error_t;

uint8_t f_buffer[BUFFER_SIZE];       // Buffer for file operations
// Buffer for file seek test (write)
uint8_t test_wr_buffer[SEEK_TEST_LENGTH] = {'_', '_', '_', '_', '\0'};
// Buffer for file seek test (read)
uint8_t test_rd_buffer[SEEK_TEST_LENGTH] = {'\0', '\0', '\0', '\0', '\0'};

sdspi_config_t sd_conf;              // Configuration for SD card SPI interface
sdspi_physical_drive_t sdc;          // Physical drive structure for SD card

fatfs_logical_drive_t fatfs_drive;   // Logical drive structure for the file system
FIL fatfs_file;                      // FAT file structure

file_t file_obj;                     // File handle for file operations

test_fs_error_t error = TEST_FS_NO_ERROR;  // Variable to hold file system error codes
uint8_t fs_error;                    // Variable for storing the result of file system functions

log_t logger;                        // Logger object for outputting messages
log_cfg_t config;                    // Logger configuration

// ------------------------------------------------------------------ FUNCTIONS
/**
 * @brief Function to indicate failure by toggling an LED on the board.
 */
void test_fail() {
    digital_out_init(&status, TEST_FAILED_PIN);
    while(1) {
        digital_out_toggle(&status);
        Delay_ms(1000);              // Toggle the failure LED every 1 second
    }
}

// ----------------------------------------------------------------- USER CODE
/**
 * @brief Main function demonstrating the file system operations on an SD card.
 * The function performs the following operations:
 *   - Initialize the physical drive (SD card)
 *   - Mount the FAT file system
 *   - Format the drive
 *   - Create and manipulate a file (__DATA.TXT)
 *   - Test file seek functionality
 */
int main( void ) {
    /* Do not remove this line or clock might not be set correctly. */
    #ifdef PREINIT_SUPPORTED
    preinit();
    #endif

    uint32_t i;

    // Initialize logging over USB UART
    LOG_MAP_USB_UART( config );
    log_init(&logger, &config);

    log_printf( &logger, "TEST STARTED\n");

    // Fill buffer with test data for file write operations
    for (i = 0; i < BUFFER_SIZE; i++) {
        f_buffer[i] = i & 0xFF;
    }

    // Configure SPI pins for SD card on mikroBUS™ socket 1
    sd_conf.spi_sck = MIKROBUS_1_SCK;
    sd_conf.spi_miso = MIKROBUS_1_MISO;
    sd_conf.spi_mosi = MIKROBUS_1_MOSI;
    sd_conf.spi_cs = MIKROBUS_1_CS;

    // Initialize SD card (physical drive)
    fs_error = sdspi_physical_drive_init(&sdc, &sd_conf);
    if (0 != fs_error) {
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    // Initialize the FAT file system on the SD card
    fs_error = fatfs_initialize(&fatfs_drive);
    if (0 != fs_error) {
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    // Mount the file system to drive number 3
    fs_error = file_system_mount(&fatfs_drive.base, "3:", &sdc.base);
    if (0 != fs_error) {
        error = TEST_FS_ERROR_MOUNT;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    log_printf( &logger, "FORMATTING STARTED\n");
    // Format the SD card (logical drive 3)
    fs_error = file_system_format("3:");
    if (0 != fs_error) {
        error = TEST_FS_ERROR_FORMAT;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }
    log_printf( &logger, "FORMATTING COMPLETED\n");

    // Create and open a file on drive 3 (__DATA.TXT)
    fs_error = file_open(&file_obj, &fatfs_file, "3:/__DATA.TXT",
                                                 FS_FILE_OPEN_APPEND |
                                                 FS_FILE_WRITE |
                                                 FS_FILE_READ);
    if (0 != fs_error) {
        error = TEST_FS_ERROR_FOPEN;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    // Write buffer to the file
    fs_error = file_write(&file_obj, f_buffer, BUFFER_SIZE);
    if (fs_error != 0) {
        error = TEST_FS_ERROR_FWRITE;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    // Write another buffer to the file (append)
    fs_error = file_write(&file_obj, f_buffer, BUFFER_SIZE);
    if (fs_error != 0) {
        error = TEST_FS_ERROR_FWRITE;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    // Close the file
    fs_error = file_close(&file_obj);
    if (fs_error != 0) {
        error = TEST_FS_ERROR_FCLOSE;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    log_printf( &logger, "FILE __DATA.TXT CREATED\n");

    // Open the file again for file seek test
    fs_error = file_open(&file_obj, &fatfs_file, "3:/__DATA.TXT",
                                                 FS_FILE_OPEN_APPEND |
                                                 FS_FILE_WRITE |
                                                 FS_FILE_READ);
    if (fs_error != 0) {
        error = TEST_FS_ERROR_FOPEN;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    // Seek to 16 characters before the end of the file
    fs_error = file_seek(&file_obj, -16, SEEK_END);
    if (fs_error != 0) {
        error = TEST_FS_ERROR_FSEEK;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    // Write test data at the new cursor position
    fs_error = file_write(&file_obj, test_wr_buffer, SEEK_TEST_LENGTH - 1);
    if (fs_error != 0) {
        error = TEST_FS_ERROR_FWRITE;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    // Seek back to the same position and read the data
    fs_error = file_seek(&file_obj, -16, SEEK_END);
    if (fs_error != 0) {
        error = TEST_FS_ERROR_FSEEK;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    // Read the previously written data and verify
    fs_error = file_read(&file_obj, test_rd_buffer, SEEK_TEST_LENGTH - 1);
    if (fs_error != 0) {
        error = TEST_FS_ERROR_FREED;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    // Close the file after test
    fs_error = file_close(&file_obj);
    if (fs_error != 0) {
        error = TEST_FS_ERROR_FCLOSE;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    // Compare the read data with written data to verify the seek operation
    if (memcmp(test_wr_buffer, test_rd_buffer, SEEK_TEST_LENGTH - 1) != 0) {
        error = TEST_FS_ERROR_FSEEK;
        log_printf( &logger, "FAIL LINE: %d\n", (uint16_t)__LINE__);
        test_fail();
    }

    log_printf( &logger, "TEST FSEEK END COMPLETED\n");
    log_printf( &logger, "TEST COMPLETED\n");

    // Indicate successful test completion with LED
    while (1) {
        digital_out_init(&status, TEST_SUCCESS_PIN);
        while (1) {
            digital_out_toggle(&status);
            Delay_ms(1000);          // Toggle the success LED every 1 second
        }
    }

    return TEST_FS_NO_ERROR;          // Return with no error code
}

// ----------------------------------------------------------------------- END