Skip to content

概念先知道

  • 文件系统:把 Flash/SD 卡的扇区组织成“文件夹+文件”的软件层,让你用 f_open/f_write 而不是裸地址读写。
  • FATFS:一个开源的嵌入式文件系统库(支持 FAT12/FAT16/FAT32/exFAT),例程通过 SD 卡驱动(SDH)挂载到 /sd
  • 挂载(Mount):把文件系统“接上”某个路径。例程 f_mount(&fs, "/sd", 1) 后,路径 /sd/test_file.txt 就指向卡上的文件。
  • 簇(Cluster):文件系统的最小分配单位。例程格式化为 exFAT、簇大小 32 扇区(16KB),打印的 Cluster size 就是它。
  • 读写校验:写一遍数据再读回来逐字节比对,能暴露位翻转/坏块等问题,是存储测试的常见做法。

例程功能简介

本页对应博流官方 SDK 的 fatfs 例程(examples/fatfs),演示 SD 卡 FATFS 文件系统的完整用法:

  • 初始化 SD 卡(board_sdh_gpio_init)并注册驱动(fatfs_sdh_driver_register);
  • 挂载 /sd;若卡上没有文件系统,自动用 exFAT 格式化(f_mkfs)后重新挂载,并打印文件系统类型/容量/簇大小;
  • 循环 5 轮执行 写入测试(32KB × 1000 次,统计速度)、读取测试数据校验Check Test 逐字节比对);
  • 出错时打印错误位置并用 DBG_HEXDUMP 输出出错区数据,方便排查;
  • 若开启 CONFIG_NEWLIB_FATFS,还会演示标准 C 库 fopen/fprintf 写文件。

提示

本页用 SD 卡(SDH 接口);SDK 另有一个 examples/littlefs 例程把文件系统跑在片内 Flash 分区上,见本栏目「LittleFS 文件系统」页。

操作步骤

1
硬件准备

需要一张 TF/SD 卡(建议先格式化为 FAT32/exFAT)插入开发板 SD 卡槽,或按 board_sdh_gpio_init() 的引脚定义外接 SD 卡模块。

2
进入例程目录

在终端进入 SDK 的 FATFS 例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/fatfs
3
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
4
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
5
运行验证

打开串口助手(波特率 2000000)。例程自动挂载 SD 卡(无文件系统时先格式化),循环 5 轮执行 写→读→校验 测试:写 32KB×1000 次、再整读比对,串口打印 Write Test Succeed!、读写速度(KB/s)与 All Data Is Good!

代码执行流程

例程从启动到完成测试的流程如下:

例程调用的 API 介绍

f_mount(&fs, "/sd", 1)

挂载/注册文件系统到路径。第三个参数为 1 表示立即挂载。

参数

  • fsFATFS 文件系统对象
  • path:挂载路径(例程 /sd
  • opt1 立即挂载

返回值FR_OK(0)成功;FR_NO_FILESYSTEM 等错误码

f_mkfs("/sd", &fs_para, workbuf, bufsize)

格式化设备。例程用 MKFS_PARM 指定 FM_EXFAT、簇大小 32 扇区。

参数

  • path:要格式化的逻辑驱动器
  • parMKFS_PARM 格式参数
  • work / sz_work:工作缓冲区与大小

返回值FR_OK(0)成功;其它为错误码

f_open / f_write / f_read / f_close(...)

打开、写入、读取、关闭文件。写测试用 FA_CREATE_ALWAYS | FA_WRITE 创建 /sd/test_file.txt,读测试用 FA_OPEN_EXISTING | FA_READ

参数

  • fpFIL 文件对象
  • path:文件路径
  • buff / btr:数据缓冲与请求字节数
  • bw:实际读写字节数输出

返回值FR_OK(0)成功;其它为错误码

bflb_mtimer_get_time_ms()

获取毫秒时间戳,例程用它计算读写总耗时与速率。

参数:无

返回值:毫秒时间戳

完整代码

以下为 fatfs/main.c 完整源码,与官方示例一致,默认折叠,点击展开:

📜 点击展开 fatfs/main.c 完整代码
c
#include "bflb_mtimer.h"
#include "board.h"
#include "fatfs_diskio_register.h"
#include "ff.h"

#if defined(CONFIG_FREERTOS) && CONFIG_FREERTOS
#include <FreeRTOS.h>
#include "semphr.h"
#endif

#define DBG_TAG "MAIN"
#include "log.h"

#if defined(CONFIG_FREERTOS) && CONFIG_FREERTOS
static TaskHandle_t test_handle;
#endif

#define TEST_DATA_SIZE (1024 * 32)
#define TEST_RW_CNT    (1000)
#define TEST_CYCLE_NUM (5)

FATFS fs;
__attribute((aligned(64))) static uint32_t workbuf[FF_MAX_SS * 4];

MKFS_PARM fs_para = {
    .fmt = FM_EXFAT,     /* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */
    .n_fat = 0,          /* Number of FATs */
    .align = 0,          /* Data area alignment (sector) */
    .n_root = 0,         /* Number of root directory entries */
    .au_size = 512 * 32, /* Cluster size (byte) */
};

int filesystem_init(void)
{
    FRESULT ret;

    board_sdh_gpio_init();

    fatfs_sdh_driver_register();

    ret = f_mount(&fs, "/sd", 1);

    if (ret == FR_NO_FILESYSTEM) {
        LOG_W("No filesystem yet, try to be formatted...\r\n");

        ret = f_mkfs("/sd", &fs_para, workbuf, sizeof(workbuf));
        if (ret != FR_OK) {
            LOG_E("fail to make filesystem %d\r\n", ret);
            return -1;
        }

        LOG_W("The formatting was successful. Try mount again\r\n");

        ret = f_mount(&fs, "/sd", 1);
    }

    if (ret == FR_OK) {
        LOG_D("Succeed to mount filesystem\r\n");
        LOG_I("FileSystem type: %s\r\n", fs.fs_type == 1 ? "FAT12" :
                                         fs.fs_type == 2 ? "FAT16" :
                                         fs.fs_type == 3 ? "FAT32" :
                                         fs.fs_type == 4 ? "exFAT" :
                                                           "unknown");
        LOG_I("   Volume  size: %d MByte\r\n", (uint32_t)((uint64_t)(fs.n_fatent - 2) * fs.csize * 512 / 1024 / 1024));
        LOG_I("   Cluster size: %d-sectors (%d-Byte)\r\n", fs.csize, fs.csize * 512);

    } else {
        LOG_E("fail to mount filesystem,error= %d\r\n", ret);
        LOG_E("SD card might fail to initialise.\r\n");
        return -1;
    }

    return 0;
}

#define SDU_DATA_CHECK 1

char test_data[512] =
    "I've been reading books of old \r\n\
    The legends and the myths \r\n\
    Achilles and his gold \r\n\
    Hercules and his gifts \r\n\
    Spiderman's control \r\n\
    And Batman with his fists\r\n\
    And clearly I don't see myself upon that list\r\n\
    But she said, where'd you wanna go?\r\n\
    How much you wanna risk?\r\n\
    I'm not looking for somebody\r\n\
    With some superhuman gifts\r\n\
    Some superhero\r\n\
    Some fairytale bliss\r\n\
    Just something I can turn to\r\n\
    Somebody I can kiss\r\n\
    I want something just like this\r\n\r\n";

__attribute((aligned(64))) BYTE RW_Buffer[TEST_DATA_SIZE] = { 0 };
#if SDU_DATA_CHECK
__attribute((aligned(64))) BYTE Check_Buffer[TEST_DATA_SIZE] = { 0 };
#endif

int fatfs_write_read_test()
{
    FRESULT ret;
    FIL fnew;
    UINT fnum;

    uint32_t time_node, i, j;

    /* fill test data to buff */
    for (uint32_t i = 0; i < TEST_DATA_SIZE; i++) {
        RW_Buffer[i] = test_data[i % sizeof(test_data)];
        Check_Buffer[i] = test_data[i % sizeof(test_data)];
    }

    /* write test */
    LOG_RI("\r\n");
    LOG_I("******************** be about to write test... **********************\r\n");
    ret = f_open(&fnew, "/sd/test_file.txt", FA_CREATE_ALWAYS | FA_WRITE);
    if (ret == FR_OK) {
        time_node = (uint32_t)bflb_mtimer_get_time_ms();
        /*write into file*/
        // ret = f_write(&fnew, RW_Buffer, 1024, &fnum);
        for (i = 0; i < TEST_RW_CNT; i++) {
            ret = f_write(&fnew, RW_Buffer, TEST_DATA_SIZE, &fnum);
            if (ret) {
                break;
            }
        }

        /* close file */
        ret |= f_close(&fnew);
        /* get time */
        time_node = (uint32_t)bflb_mtimer_get_time_ms() - time_node;

        if (ret == FR_OK) {
            LOG_I("Write Test Succeed! \r\n");
            LOG_I("Single data size:%d Byte, Write the number:%d, Total size:%d KB\r\n", TEST_DATA_SIZE, i, TEST_DATA_SIZE * i / 1024);
            LOG_I("Time:%dms, Write Speed:%d KB/s \r\n", time_node, (TEST_DATA_SIZE * i / 1024) * 1000 / time_node);
        } else {
            LOG_E("Fail to write files(%d) num:%d\n", ret, i);
            return -1;
        }
    } else {
        LOG_E("Fail to open or create files: %d.\r\n", ret);
        return -1;
    }

    /* read test */
    LOG_RI("\r\n");
    LOG_I("******************** be about to read test... **********************\r\n");
    ret = f_open(&fnew, "/sd/test_file.txt", FA_OPEN_EXISTING | FA_READ);
    if (ret == FR_OK) {
        time_node = (uint32_t)bflb_mtimer_get_time_ms();

        // ret = f_read(&fnew, RW_Buffer, 1024, &fnum);
        for (i = 0; i < TEST_RW_CNT; i++) {
            ret = f_read(&fnew, RW_Buffer, TEST_DATA_SIZE, &fnum);
            if (ret) {
                break;
            }
        }
        /* close file */
        ret |= f_close(&fnew);
        /* get time */
        time_node = (uint32_t)bflb_mtimer_get_time_ms() - time_node;

        if (ret == FR_OK) {
            LOG_I("Read Test Succeed! \r\n");
            LOG_I("Single data size:%d Byte, Read the number:%d, Total size:%d KB\r\n", TEST_DATA_SIZE, i, TEST_DATA_SIZE * i / 1024);
            LOG_I("Time:%dms, Read Speed:%d KB/s \r\n", time_node, (TEST_DATA_SIZE * i / 1024) * 1000 / time_node);
        } else {
            LOG_E("Fail to read file: (%d), num:%d\n", ret, i);
            return -1;
        }
    } else {
        LOG_E("Fail to open files.\r\n");
        return -1;
    }

    /* check data */
#if SDU_DATA_CHECK
    LOG_RI("\r\n");
    LOG_I("******************** be about to check test... **********************\r\n");
    ret = f_open(&fnew, "/sd/test_file.txt", FA_OPEN_EXISTING | FA_READ);
    if (ret == FR_OK) {
        // ret = f_read(&fnew, RW_Buffer, 1024, &fnum);
        for (i = 0; i < TEST_RW_CNT; i++) {
            memset(RW_Buffer, 0x55, TEST_DATA_SIZE);
            ret = f_read(&fnew, RW_Buffer, TEST_DATA_SIZE, &fnum);
            if (ret) {
                break;
            }
            for (j = 0; j < TEST_DATA_SIZE; j++) {
                if (RW_Buffer[j] != Check_Buffer[j]) {
                    break;
                }
            }
            if (j < TEST_DATA_SIZE) {
                break;
            }
        }
        /* close file */
        ret |= f_close(&fnew);

        if (ret == FR_OK) {
            if (i < TEST_RW_CNT || j < TEST_DATA_SIZE) {
                LOG_I("Check Test Error! \r\n");
                LOG_I("Data Error!  Num:%d/%d, Byte:%d/%d, 0x%X(%c)->0x%X(%c) \r\n",
                      i, TEST_RW_CNT, j, TEST_DATA_SIZE, Check_Buffer[j], Check_Buffer[j], RW_Buffer[j], RW_Buffer[j]);

                uint32_t n = j & (~511);
                DBG_VALUE(n);
                LOG_I("Error data offset: 0x%04X\r\n", j - n);

                char *check_data = (char *)&Check_Buffer[n];
                DBG_HEXDUMP(check_data, 1024);

                char *read_data = (char *)&RW_Buffer[n];
                DBG_HEXDUMP(read_data, 1024);

                return -1;
            } else {
                LOG_I("Check Test Succeed! \r\n");
                LOG_I("All Data Is Good! \r\n\r\n");
            }

        } else {
            LOG_E("Fail to read file: (%d), num:%d\n", ret, i);
            return -1;
        }
    } else {
        LOG_E("Fail to open files.\r\n");
        return -1;
    }
#endif

    return 0;
}

void fatfs_test_main(void *param)
{
    (void)param;

    for (int test_cnt = 1; test_cnt <= TEST_CYCLE_NUM; test_cnt++) {
        LOG_W("test_cnt: %d\r\n", test_cnt);

        if (filesystem_init() < 0) {
            break;
        }
        if (fatfs_write_read_test() < 0) {
            break;
        }

#if defined(CONFIG_NEWLIB) && CONFIG_NEWLIB && defined(CONFIG_NEWLIB_FATFS) && CONFIG_NEWLIB_FATFS
        FILE *fp;
        fp = fopen("/sd/hellotest.txt", "w+");
        fprintf(fp, "hello world\r\n");
        fclose(fp);
#endif

        f_unmount("/sd");

        bflb_mtimer_delay_ms(1000);
    }

    while (1) {
        bflb_mtimer_delay_ms(200);
    }
}

int main(void)
{
    board_init();

#if defined(CONFIG_FREERTOS) && CONFIG_FREERTOS
    xTaskCreate(fatfs_test_main, (char *)"test_task", 1024, NULL, configMAX_PRIORITIES - 2, &test_handle);
    vTaskStartScheduler();
#else
    fatfs_test_main(NULL);
#endif
}

FAQ

串口打印 “fail to mount filesystem” 怎么办?

多为 SD 卡接触不良或卡未插入:重新插拔、换一张卡试试。若一直报 FR_NO_FILESYSTEM,例程会自动格式化,无需手工处理。

例程会把我卡上的数据清掉吗?

只有在挂载时发现没有文件系统才会格式化;已有文件系统的卡会直接挂载,不会擦除数据。建议仍先用不重要的卡测试。

写入速度为什么比电脑上慢很多?

正常。SDH 通过 GPIO 模拟/低速接口读写,且例程单次 32KB 逐块写入,实际吞吐受限于接口与 Flash 写入时间,属预期表现。

想在自己工程里用 FATFS 怎么改?

参考例程三步:defconfig 开启 CONFIG_FATFS=y(含 SD 卡选项);board_sdh_gpio_init() + fatfs_sdh_driver_register() 初始化驱动;f_mount 挂载后即可用 f_open/f_read/f_write。注意接线与开发板 SDH 引脚一致。

Released under the MIT License. Build Time 2026-09-11 14:52:23