开源软件学习笔记 - little_flash + littlefs

目录

[1. 创建工程](#1. 创建工程)

[2. 移植(port)](#2. 移植(port))

[2.1 little_flash_spi_transfer](#2.1 little_flash_spi_transfer)

[2.2 little_flash_wait_10us](#2.2 little_flash_wait_10us)

[2.3 其他](#2.3 其他)

[3. 添加新的nor flash](#3. 添加新的nor flash)

[3.1 获取新flash的ID](#3.1 获取新flash的ID)

[3.2 擦除](#3.2 擦除)

[3.3 写](#3.3 写)

[3.4 读](#3.4 读)

[4. 添加新的nand flash](#4. 添加新的nand flash)

[4.1 读写状态寄存器](#4.1 读写状态寄存器)

[4.2 修改status读写函数](#4.2 修改status读写函数)

[5. littlefs](#5. littlefs)

[5.1 初始化lfs_config变量](#5.1 初始化lfs_config变量)

[5.2 挂载文件系统](#5.2 挂载文件系统)

[5.3 读写测试](#5.3 读写测试)


类似SFUD,支持spi nand flash,对于nand flash,最好增加文件系统,可使用little_flash直接进行flash操作,但不推荐NAND FLASH直接操作,推荐使用文件系统littlefs。little_flash的下载地址:

little_flash: SPI FLASH 通用驱动库 - Gitee.com

不过,注意这个little_flash是比较新的开源软件,还不是很完善,目前的版本是0.0.1。

littlefs主要用在微控制器和flash上,是一种嵌入式文件系统。主要有3个特点:

1)掉电恢复

在写入时即使复位或者掉电也可以恢复到上一个正确的状态。

2)擦写均衡

有效延长flash的使用寿命

3)有限的RAM/ROM

节省ROM和RAM空间

littlefs的下载地址:

littlefs-project/littlefs: A little fail-safe filesystem designed for microcontrollers

硬件平台采用FT4222H模块+SPI NAND FLASH,采用官方库v1.4.8移植,下载链接如下:

https://ftdichip.com/wp-content/uploads/2025/06/LibFT4222-v1.4.8.zip

1. 创建工程

本文档对应的工程下载地址:https://gitee.com/pq113_6/lib-ft4222.git

在官方库文件夹FT4222H\LibFT4222-v1.4.8\samples\flash_example中,复制spi_flash_quad_test_mxic,将文件夹名和里面的文件名均改为app_little_flash

用记事本将这4个文件编辑,将其中的"spi_flash_quad_test_mxic"改为"app_little_flash"

打开LibFT4222-v1.4.8\samples\LibFT4222_examples.sln,移除原来的源代码,导入新的app_little_flash.cpp。

在该项目目录内新建文件夹little_flash,将little_flash的src/inc/port三个文件夹拷贝到这个文件夹内,将这些文件导入工程,将该工程设置为启动项目,整个工程目录如下:

配置include路径,讲little_flash的inc目录和port目录路径添加到项目配置中。

将LF_PRINTF宏定义改为

cpp 复制代码
#define LF_PRINTF printf

不过这里有一个问题,little_flash底层打印都没有换行,所以这里改为函数的方式,增加换行。在little_flash_port.c中添加lf_printf函数

cpp 复制代码
static char log_buf[256];
void lf_printf(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    printf("[little_flash]");
    /* must use vprintf to print */
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    printf("%s\n", log_buf);
    va_end(args);
}

将这个宏定义改为:

cpp 复制代码
#define LF_PRINTF lf_printf

将little_flash_wait_ms改为

cpp 复制代码
static void little_flash_wait_ms(uint32_t ms){
    Sleep(ms);
}

将main函数改为(这点很奇怪)

cpp 复制代码
int main(int argc, char* argv[])

最后将mix部分的代码删除,此时应该可以编译通过。

2. 移植(port)

作者在little_flash_port.c里面已经放了需要的接口函数,逐步实现这些函数即可。

2.1 little_flash_spi_transfer

这个是SPI读写的函数,参考SFDU的方式开源软件学习笔记 - 移植SFUD-CSDN博客

新建一个little_flash_spi_transfer.h,同样增加一个用户数据,同样记录FT4222H的句柄

cpp 复制代码
typedef struct {
    FT_HANDLE handle;
}lf_user_data_s;

作者应该还没有实现QSPI部分,所以这里也是只实现了SPI

cpp 复制代码
static lf_err_t little_flash_spi_transfer(const little_flash_t *lf,uint8_t *tx_buf, uint32_t tx_len, uint8_t *rx_buf, uint32_t rx_len){
    lf_err_t result = LF_ERR_OK;
    lf_user_data_s* spi_dev = (lf_user_data_s *)(lf->spi.user_data);
    FT4222_STATUS ftStatus = FT4222_OK;
    uint16_t sizeTransferred = 0;
    bool endTransfer = true;
    if ((rx_buf != NULL) && (rx_len > 0))
        endTransfer = false;
    if ((tx_buf != NULL) && (tx_len > 0))
    {
        ftStatus = FT4222_SPIMaster_SingleWrite(
            spi_dev->handle,
            (uint8_t*)tx_buf,
            tx_len,
            &sizeTransferred,
            endTransfer
        );
        if (ftStatus != FT4222_OK)
        {
            LF_DEBUG("spi write fail");
            return LF_ERR_NO_FLASH;
        }

        if (sizeTransferred != tx_len)
        {
            LF_DEBUG("spi write size is not match");
            return LF_ERR_WRITE;
        }

    }

    if ((rx_buf != NULL) && (rx_len > 0))
    {
        ftStatus = FT4222_SPIMaster_SingleRead(
            spi_dev->handle,
            rx_buf,
            rx_len,
            &sizeTransferred,
            true
        );
        if (ftStatus != FT4222_OK)
        {
            LF_DEBUG("spi read fail");
            return LF_ERR_NO_FLASH;
        }

        if (sizeTransferred != rx_len)
        {
            LF_DEBUG("spi read size is not match");
            return LF_ERR_READ;
        }

    }
    return result;
}

2.2 little_flash_wait_10us

10us延时程序,在windows里面需要改一下,只能是非精准的延时

cpp 复制代码
static void little_flash_wait_10us(void){
    for (volatile uint32_t i = 0; i < 20; i++) {
        // 每个迭代约0.5微秒(根据CPU速度调整)
    }
}

2.3 其他

其他函数little_flash_wait_ms、little_flash_malloc、little_flash_free和little_flash_port_init都不需要改动。

3. 添加新的nor flash

当前版本只支持2个Flash,两种类型(nor/nand),在little_flash_table.h里面有相应的配置:

cpp 复制代码
#define LITTLE_FLASH_CHIP_TABLE                                                                              \
{                                                                                                            \
    {.name="W25N01GVZEIG", .manufacturer_id=LF_MF_ID_WINBOND, .device_id=0xAA21, .type=LF_DRIVER_NAND_FLASH, .capacity=128L*1024L*1024L, .erase_cmd=0xD8, .erase_size=64L*2048L},     \
    {.name="W25Q128FVSG",  .manufacturer_id=LF_MF_ID_WINBOND, .device_id=0x4018, .type=LF_DRIVER_NOR_FLASH,  .capacity=16L*1024L*1024L,  .erase_cmd=0x20, .erase_size=4096L}             \
}

所以一般需要自己根据自己的平台添加。先从简单的开始,添加新的nor flash。

3.1 获取新flash的ID

在main函数中添加初始化代码,先定义全局变量lf_flash_user

cpp 复制代码
lf_user_data_s lf_flash_user[1] = { 0 };

将打开的FT4222H句柄赋值给这个变量

cpp 复制代码
lf_flash_user->handle = ftHandle;
little_flash_t lf_flash = { 0 };
lf_flash.spi.user_data = (void*)lf_flash_user;

初始化little_flash

cpp 复制代码
little_flash_init();
little_flash_device_init(&lf_flash);
printf("little_flash_device_init OK\n");

找到little_flash_device_init,将ID信息打印部分打开

cpp 复制代码
LF_DEBUG("recv_data [0]:0x%02X [1]:0x%02X [2]:0x%02X [3]:0x%02X",recv_data[0],recv_data[1],recv_data[2],recv_data[3]);

运行这个代码,可以看到log信息:

bash 复制代码
[little_flash]Welcome to use little flash V0.0.1 .
[little_flash]Github Repositories https://github.com/PeakRacing/little_flash .
[little_flash]Gitee Repositories https://gitee.com/PeakRacing/little_flash .
[little_flash]recv_data [0]:0xC8 [1]:0x40 [2]:0x16 [3]:0xC8
[little_flash]NOT fonud flash
little_flash_device_init OK

在little_flash_table.h里面根据ID信息添加新的flash

cpp 复制代码
{.name="GD25Q32",  .manufacturer_id=LF_MF_ID_GIGADEVICE, .device_id=0x4016, .type=LF_DRIVER_NOR_FLASH,  .capacity=4L*1024L*1024L,  .erase_cmd=0x20, .erase_size=4096L}

而LF_MF_ID_GIGADEVICE的定义如下:

cpp 复制代码
#define LF_MF_ID_GIGADEVICE                             0xC8

device_id对应9FH命令读回来的第2、3字节。

capacity设置的是flash的容量,字节为单位。

erase_cmd是flash的擦除命令,一般会支持多个擦除方式,一般选择Sector Erase,对应命令是0x20.

擦除大小erase_size对应擦除命令配置。

再运行程序后,log信息如下:

bash 复制代码
[little_flash]Welcome to use little flash V0.0.1 .
[little_flash]Github Repositories https://github.com/PeakRacing/little_flash .
[little_flash]Gitee Repositories https://gitee.com/PeakRacing/little_flash .
[little_flash]recv_data [0]:0xC8 [1]:0x40 [2]:0x16 [3]:0xC8
[little_flash]JEDEC ID: manufacturer_id:0xC8 device_id:0x4016
[little_flash]little flash fonud flash GD25Q32

3.2 擦除

擦除对应的函数是little_flash_erase,其原型如下:

cpp 复制代码
lf_err_t little_flash_erase(const little_flash_t *lf, uint32_t addr, uint32_t len)

擦除地址和长度最好都符合erase_size的整数倍。这里是擦除一个sector:

cpp 复制代码
little_flash_erase(&lf_flash, 0, lf_flash.chip_info.erase_size);

3.3 写

写数据的函数是little_flash_write,其原型如下:

cpp 复制代码
lf_err_t little_flash_write(const little_flash_t *lf, uint32_t addr, const uint8_t *data, uint32_t len)

测试代码是写入16个字节数据

cpp 复制代码
const uint8_t wr_buf[16] = {
    0xAA, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x55,
};
little_flash_write(&lf_flash, 0, wr_buf, 16);

3.4 读

读数据的函数是little_flash_read,其原型如下:

cpp 复制代码
lf_err_t little_flash_read(const little_flash_t *lf, uint32_t addr, uint8_t *data, uint32_t len)

测试代码是将之前写入的数据读回来判断

cpp 复制代码
uint8_t rd_buf[16];
int i = 0;
for (i = 0; i < 16; i++)
    rd_buf[i] = 0;
little_flash_read(&lf_flash, 0, rd_buf, 16);
printf("******SPI Read Test******\n");
for (i = 0; i < 16; i++)
{
    printf("0x%02x ", rd_buf[i]);
    if (((i + 1) % 8) == 0)
        printf("\n");
}
printf("\n");

log信息如下:

cpp 复制代码
******SPI Read Test******
0xaa 0x01 0x02 0x03 0x04 0x05 0x06 0x07
0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x55

说明读写已经可以了。

4. 添加新的nand flash

对nand flash的支持不是太完善,需要修改一些源代码。

4.1 读写状态寄存器

Nand Flash的状态寄存器的读写方式与Nor Flash的不同,WInbond的W25N01GV和MX35LF1G24AD(MXIC这部分是Feature)为例:

即支持更多的Feature,第一个字节Winbond的支持2种方式,而MXIC只支持0F/1F,第二个字节对应的是寄存器地址,Winbond和MXIC的地址定义分别如下:

可以看到MXIC的寄存器更多,但是最重要的tatus寄存器地址是一样的。

给结构体little_flash_chipinfo_t增加2个变量

cpp 复制代码
uint8_t read_status_cmd;                     
uint8_t write_status_cmd;

修改LITTLE_FLASH_CHIP_TABLE,增加这2个变量的初始化值。

cpp 复制代码
#define LITTLE_FLASH_CHIP_TABLE                                                     \
{                                                                                   \
    {.name="W25N01GVZEIG", .manufacturer_id=LF_MF_ID_WINBOND, .device_id=0xAA21,    \
        .type=LF_DRIVER_NAND_FLASH, .capacity=128L*1024L*1024L, .erase_cmd=0xD8,    \
        .erase_size=64L*2048L, .read_status_cmd = 0x05, .write_status_cmd = 0x01},   \
    {.name="MX35LF512", .manufacturer_id=LF_MF_ID_MXIC, .device_id=0x12C2,          \
        .type=LF_DRIVER_NAND_FLASH, .capacity=64L*1024L*1024L, .erase_cmd=0xD8,     \
        .erase_size=64L*2048L, .read_status_cmd = 0x0f, .write_status_cmd = 0x1f},   \
    {.name="W25Q128FVSG",  .manufacturer_id=LF_MF_ID_WINBOND, .device_id=0x4018,    \
        .type=LF_DRIVER_NOR_FLASH,  .capacity=16L*1024L*1024L,  .erase_cmd=0x20,    \
        .erase_size=4096L, .read_status_cmd = 0x05, .write_status_cmd = 0x01},       \
    {.name="GD25Q32",  .manufacturer_id=LF_MF_ID_GIGADEVICE, .device_id=0x4016,     \
        .type=LF_DRIVER_NOR_FLASH,  .capacity=4L*1024L*1024L,  .erase_cmd=0x20,     \
        .erase_size=4096L, .read_status_cmd = 0x05, .write_status_cmd = 0x01}        \
}

4.2 修改status读写函数

将little_flash_write_status和little_flash_read_status里面第一个字节命令改为配置值。

cpp 复制代码
//cmd_data[0]=LF_CMD_WRITE_STATUS_REGISTER;
cmd_data[0] = lf->chip_info.write_status_cmd;;

//cmd_data[0]=LF_CMD_READ_STATUS_REGISTER;
cmd_data[0] = lf->chip_info.read_status_cmd;

此时flash读写测试可以看到log信息

bash 复制代码
[little_flash]Welcome to use little flash V0.0.1 .
[little_flash]Github Repositories https://github.com/PeakRacing/little_flash .
[little_flash]Gitee Repositories https://gitee.com/PeakRacing/little_flash .
[little_flash]JEDEC ID [0]:0x00 [1]:0xC2 [2]:0x12 [3]:0xC2
[little_flash]JEDEC ID: manufacturer_id:0xC2 device_id:0x12C2
[little_flash]little flash fonud flash MX35LF512
little_flash_device_init OK
[little_flash]erase command:d8,0,0,0
[little_flash]erase wait time:2
******SPI Read Test******
0xaa 0x01 0x02 0x03 0x04 0x05 0x06 0x07
0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x55

5. littlefs

Nand Flash推荐使用文件系统littlefs实现读写,将littlefs源代码拷贝到工程目录,将对应的源文件增加到工程中。

然后加入这2个头文件

cpp 复制代码
#include "littlefs\lfs.h"
#include "littlefs\lfs_util.h"

项目属性 → C/C++ → 语言 → C 语言标准 → 选择"ISO C11 标准"

5.1 初始化lfs_config变量

lfs的核心移植部分就是初始这个lfs_config变量,新增一个函数lsf_init_cfg初始化这个变量

cpp 复制代码
void lsf_init_cfg(struct lfs_config *cfg, little_flash_t* lf_flash) 
{
    memset(cfg, 0, sizeof(lfs_config));  // 清零结构体

    cfg->context = lf_flash; 
    cfg->read = lf_block_device_read;
    cfg->prog = lf_block_device_prog;
    cfg->erase = lf_block_device_erase;
    cfg->sync = lf_block_device_sync;

    // 配置块设备参数
    cfg->read_size = lf_flash->chip_info.read_size;
    cfg->prog_size = lf_flash->chip_info.prog_size;
    cfg->block_size = lf_flash->chip_info.erase_size;
    cfg->block_count = lf_flash->chip_info.capacity / lf_flash->chip_info.erase_size;
    cfg->cache_size = lf_flash->chip_info.prog_size;
    cfg->lookahead_size = lf_flash->chip_info.prog_size;
    cfg->block_cycles = 500;
}
  • context:可用于将信息传递给块设备操作
  • read:读取块中的区域
  • prog:对块中的区域进行编程。该块必须之前已被擦除。
  • erase:擦除块。在编程之前,必须擦除块。已擦除块的状态未定义
  • sync:同步底层块设备的状态。这个函数用于将数据与flash同步使用,对于使用缓存的情况需要添加。
  • read_size:读取的块的最小大小(以字节为单位)。所有读取操作都将是该值的倍数。
  • prog_size:块编程的最小大小(字节)。所有编程操作都将是该值的倍数。
  • block_size:可擦除块的大小(字节)。这不会影响ram消耗,并且可能大于物理擦除大小。但是,非内联文件至少占用一个块。必须是读取和编程大小的倍数。
  • block_count:设备上可擦除的块数。为零时默认使用存储在磁盘上的block_count。
  • cache_size:块缓存的大小(字节)。每个缓存在RAM中缓冲一个块的一部分。这些小块需要一个读缓存、一个编程缓存和每个文件一个额外的缓存。更大的缓存可以通过存储更多数据和减少磁盘访问次数来提高性能。必须是读取和程序大小的倍数,以及块大小的因子。
  • lookahead_size:先行缓冲区的大小(以字节为单位)。更大的先行缓冲区会增加分配过程中发现的块数。先行缓冲区存储为紧凑的位图,因此RAM的每个字节可以跟踪8个块。
  • block_cycles:ittlefs清除元数据日志并将元数据移动到另一个块之前的擦除周期数。建议值在100-1000的范围内,较大的值具有更好的性能,但擦写均衡分布不太一致。

然后调用这个函数初始化cfg变量

cpp 复制代码
struct lfs_config cfg;
lsf_init_cfg(&cfg, &lf_flash);

5.2 挂载文件系统

cpp 复制代码
lfs_t lfs;
// mount the filesystem
int err = lfs_mount(&lfs, &cfg);

// reformat if we can't mount the filesystem
// this should only happen on the first boot
if (err) {
    lfs_format(&lfs, &cfg);
    lfs_mount(&lfs, &cfg);
}

如果挂载文件系统失败,则格式化存储设备,重新挂载一次。

5.3 读写测试

往文件test中写入16个字节数据,然后在读回来这16个字节数据,打印出来看。

cpp 复制代码
lfs_file_t file;
const uint8_t wr_buf[16] = {
    0xAA, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x55,
};
uint8_t rd_buf[16];
uint8_t i;
for (i = 0; i < 16; i++)
    rd_buf[i] = 0;
lfs_file_open(&lfs, &file, "test", LFS_O_RDWR | LFS_O_CREAT);
size_t wr_size = lfs_file_write(&lfs, &file, wr_buf, 16);
if (wr_size != 16) {
    printf("Write failed! Written: %d\n", wr_size);
}
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET);
size_t rd_size = lfs_file_read(&lfs, &file, rd_buf, 16);
if (rd_size != 16) {
    printf("Read failed! Read: %d\n", rd_size);
}
lfs_file_close(&lfs, &file);
printf("******lfs read test******\n");
for (i = 0; i < 16; i++)
{
    printf("0x%02x ", rd_buf[i]);
    if (((i + 1) % 8) == 0)
        printf("\n");
}
printf("\n");
相关推荐
暗然而日章1 小时前
C++基础:Stanford CS106L学习笔记 2 初始化与引用
c++·笔记·学习
不羁的木木1 小时前
【开源鸿蒙跨平台开发学习笔记】Day07:React Native 开发 HarmonyOS-GitCode口袋工具开发-3
学习·开源·harmonyos
yoyo君~1 小时前
深入理解PX4飞控系统:多线程并发、原子操作与单例模式完全指南
学习·单例模式·机器人·无人机
山土成旧客1 小时前
【Python学习打卡-Day17】从二分类到多分类:ROC曲线、三大平均指标与风控利器MCC/KS
python·学习·分类
暗然而日章1 小时前
C++基础:Stanford CS106L学习笔记 5 内存与指针
c++·笔记·学习
秋深枫叶红1 小时前
嵌入式第二十六篇——数据结构双向链表
c语言·数据结构·学习·链表
匠心网络科技1 小时前
前端框架-框架为何应运而生?
前端·javascript·vue.js·学习
锦锦锦aaa1 小时前
【版图面试之60问】
经验分享·笔记
影林握雪1 小时前
M|窃听风暴 Das Leben der Anderen (2006)
经验分享·笔记·其他·生活