• STM32外部Flash移植FATFS笔记


    FatFs是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由AISI C语言编写并且完全独立于底层的I/O介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如8051PICAVRSHZ80H8ARM等。 FatFs支持FAT12FAT16FAT32等格式。

    目录

    下载FatFs源码

    认识FATFS源码结构

     FATFS文件系统移植步骤

    1. 定义自己的设备类型编号

    2. 修改DSTATUS disk_initialize 方法,初始化W25Q128设备

    3. 完成DSTATUS disk_status 函数

    4.对芯片的读取操作,完善DRESULT disk_read操作

    5. 完成对芯片的写操作,完善DRESULT disk_write

    6. 完成一些杂项操作DRESULT disk_ioctl

    7. 完善系统要求的get_fattime方法

    ffconf.h 的配置

    应用层调用

    验证结果 

    参考代码


    下载FatFs源码

    FatFs - Generic FAT Filesystem Module

    认识FATFS源码结构

    文件

    说明

    备注

    ffsystem.c

    FatF用户提供的操作系统相关函数的示例代码

     

    ffunicode.c

    文件系统支持的语言编码

    不需要修改

    ffconf.h

    文件系统配置项

    根据需求修改

    ff.c

    FatFs核心文件,文件管理的实现
    方法。该文件独立于底层介质操作文件
    的函数,利用这些函数实现文件的读写。

    不需要修改

    diskio.c

    包含底层存储介质的操作函数,
    这些函数需要用户自己实现,主要添加
    底层驱动函数。

    集成驱动文件

    函数

    条件(ffconf.h)

    备注

    disk_status
    disk_initialize
    disk_read

    总是需要

    底层设备驱动函数

    disk_write
    get_fattime
    disk_ioctl (CTRL_SYNC)

    FF_FS_READONLY == 0

    disk_ioctl (GET_SECTOR_COUNT)
    disk_ioctl (GET_BLOCK_SIZE)

    FF_USE_MKFS == 1

    disk_ioctl (GET_SECTOR_SIZE)

    FF_MAX_SS != _MIN_SS

    disk_ioctl (CTRL_TRIM)

    FF_USE_TRIM == 1

    ff_convert
    ff_wtoupper

    FF_USE_LFN != 0

    FF_CODE_PAGE 936 添加中文文件名支持

    ff_cre_syncobj
    ff_del_syncobj
    ff_req_grant
    ff_rel_grant

    FF_FS_REENTRANT == 1

    FatFs 统支持可重入配置,需要多任务系 (一般不需要)

    ff_mem_alloc
    ff_mem_free

    FF_USE_LFN == 3

    长文件名支持,缓冲区设置在堆 空间(一般设置_USE_LFN = 2)

     

    图中的六个函数位于diskio.c文件中,再加上我们需要适当的修改宏定义,位于ffconf.h中。所以实际上我们在进行文件系统移植的时候,只需要修改ffconf.hdiskio.c两个文件

     FATFS文件系统移植步骤

    1. 定义自己的设备类型编号

    2. 修改DSTATUS disk_initialize 方法,初始化W25Q128设备

    1. DSTATUS disk_initialize (
    2. BYTE pdrv /* Physical drive nmuber to identify the drive */
    3. )
    4. {
    5. DSTATUS stat;
    6. u16 i;
    7. switch (pdrv) {
    8. case DEV_FLASH :
    9. W25Q128_Init();
    10. // 延时一小段时间
    11. i=500;
    12. while(--i);
    13. stat = disk_status(pdrv);
    14. printf("init stat = %d\r\n",stat);
    15. return stat;
    16. // case DEV_MMC :
    17. // result = MMC_disk_initialize();
    18. // // translate the reslut code here
    19. // return stat;
    20. // case DEV_USB :
    21. // result = USB_disk_initialize();
    22. // // translate the reslut code here
    23. // return stat;
    24. }
    25. return STA_NOINIT;
    26. }

    这里调用W25Q128的初始化函数,之后调用 disk_status 检测设备是否初始化成功

    3. 完成DSTATUS disk_status 函数

     上图是官方对disk_status()函数的解释。该函数只有一个形参,就是设备号用于标识不同的设备,在只有一个设备的情况下,设备号默认为0。
            该函数的返回值有:
            STA_NOINIT:标识该设备未初始化成功,未进入就绪状态。
            STA_NODISK:FatFs不引用此标志。
            STA_PROTECT:表示该设备已进行了写保护。如果设置了STA_NODISK,则无效。
            0:表示该设备初始化成功。

    1. /*-----------------------------------------------------------------------*/
    2. /* Get Drive Status */
    3. /*-----------------------------------------------------------------------*/
    4. DSTATUS disk_status (
    5. BYTE pdrv /* Physical drive nmuber to identify the drive */
    6. )
    7. {
    8. DSTATUS stat;
    9. u16 FLASH_ID;
    10. switch (pdrv) {
    11. case DEV_FLASH:
    12. FLASH_ID = Read_Manufacturer();
    13. if(FLASH_ID == NM25Q128) {
    14. stat = 0;
    15. }
    16. else
    17. {
    18. stat = STA_NOINIT;
    19. }
    20. return stat;
    21. // case DEV_MMC :
    22. // result = MMC_disk_status();
    23. // // translate the reslut code here
    24. // return stat;
    25. // case DEV_USB :
    26. // result = USB_disk_status();
    27. // translate the reslut code here
    28. // return stat;
    29. }
    30. return STA_NOINIT;
    31. }

    检测W25Q128是否能够读取芯片的厂商ID,这个ID是固定的,如果能够读取出来就返回0,代表成功,否则返回初始化失败。这样当系统层在调用的时候会给出对应的错误提示。

    4.对芯片的读取操作,完善DRESULT disk_read操作

    形参:
            第一个形参:要操作的设备号,在本次移植工程中,可选项是 DEV_FLASH(0)
            第二个形参:读取到的数据的缓冲区。
            第三个形参:要读取的扇区的个数。

    返回值:
            RES_OK (0):     读数据正常
            RES_ERROR:   读取操作期间发生了不可恢复的硬错误。
            RES_PARERR: 输入了无效参数
            RES_NOTRDY: 设备还没初始化

    函数值调用:

          f_read()---->disk_read()

    1. DRESULT disk_read (
    2. BYTE pdrv, /* Physical drive nmuber to identify the drive */
    3. BYTE *buff, /* Data buffer to store read data */
    4. LBA_t sector, /* Start sector in LBA */
    5. UINT count /* Number of sectors to read */
    6. )
    7. {
    8. DRESULT res;
    9. switch (pdrv) {
    10. case DEV_FLASH :
    11. /* 扇区偏移2MB,外部Flash文件系统空间放在SPI Flash后面14MB空间 */
    12. sector+=512;
    13. W25Q128_Read((u8 *)buff, sector * FLASH_SECTOR_SIZE, count * FLASH_SECTOR_SIZE);
    14. res = RES_OK;
    15. return res;
    16. // case DEV_MMC :
    17. // // translate the arguments here
    18. // result = MMC_disk_read(buff, sector, count);
    19. // // translate the reslut code here
    20. // return res;
    21. // case DEV_USB :
    22. // // translate the arguments here
    23. // result = USB_disk_read(buff, sector, count);
    24. // // translate the reslut code here
    25. // return res;
    26. }
    27. return RES_PARERR;
    28. }

    这里前面sector+=512 是偏移2M字节,是根据秉火的教程,Flash前2M空间会放一些其他的内容,用户可以直接进行扇区的数据操作,不使用文件系统存放的一些数据。

    w25q128 一个扇区4096byte,16个扇区一个Block,256个block是总共的存储空间。

    所以2M( 2048 * 1024 / 4096 = 512 sectors) 每次系统在读的时候,进行这么多偏移,把前2M 的空间就不操作了。

    5. 完成对芯片的写操作,完善DRESULT disk_write

     形参:
            第一个形参:要操作的设备号,在本次移植工程中,可选项是SD_CARD(0)或SPI_FLASH(1),只不过后续程序处理部分SD_CARD没进行处理。
            第二个形参:写数据的缓冲区。 要写入的数据大小为扇区大小 × 计数字节。
            第三个形参:写操作的起始扇区。比如要从第三个扇区写操作,那么该参数值就是3。
            第四个形参:写操作的扇区个数。也就是写操作要进行操作几个扇区。

    返回值:
            RES_OK (0):     写数据正常
            RES_ERROR:   写操作期间发生了不可恢复的硬错误。
            RES_WRPRT:   设备进行了写保护。
            RES_PARERR: 输入了无效参数
            RES_NOTRDY: 设备还没初始化

    函数值调用:

          f_write()---->disk_write()

    1. #if FF_FS_READONLY == 0
    2. DRESULT disk_write (
    3. BYTE pdrv, /* Physical drive nmuber to identify the drive */
    4. const BYTE *buff, /* Data to be written */
    5. LBA_t sector, /* Start sector in LBA */
    6. UINT count /* Number of sectors to write */
    7. )
    8. {
    9. DRESULT res = RES_PARERR;
    10. if (!count) {
    11. return RES_PARERR; /* Check parameter */
    12. }
    13. switch (pdrv) {
    14. case DEV_FLASH :
    15. /* 扇区偏移2MB,外部Flash文件系统空间放在SPI Flash后面14MB空间 */
    16. sector+=512;
    17. W25Q128_Sector_Erase(sector * FLASH_SECTOR_SIZE);
    18. W25Q128_Write((u8 *)buff,sector * FLASH_SECTOR_SIZE,count * FLASH_SECTOR_SIZE);
    19. res = RES_OK;
    20. return res;
    21. // case DEV_MMC :
    22. // // translate the arguments here
    23. // result = MMC_disk_write(buff, sector, count);
    24. // // translate the reslut code here
    25. // return res;
    26. // case DEV_USB :
    27. // // translate the arguments here
    28. // result = USB_disk_write(buff, sector, count);
    29. // // translate the reslut code here
    30. // return res;
    31. }
    32. return RES_PARERR;
    33. }
    34. #endif

           这里 sectors+=512 也是对文件系统上层的的操作进行偏移,保留前面512字节地址,和写操作对应。

          另外,这里write 方法有个条件编译,我们需要设置对应的宏 FF_FS_READONLY == 0 才能使用write操作。

          在进行写操作之前一定要先进行擦除操作。在W25Q128_Write((u8*)buff,sector*4096,count*4096);中含有了擦除操作,最开始没有增加擦除方法的时候,默认提示格式化系统成功,但是后面用电脑进行USB读取Flash时候,提示未格式化,也就是没有格式化成功。所以这里还是把擦除操作加上了。可以屏蔽擦除扇区一行测试。

           这里默认我们给返回写成功。也可以对写操作进行判断,增加返回值。如果某次写错误,也可能会在系统层面提示成功。这里需要注意。根据实际情况来编码。

    6. 完成一些杂项操作DRESULT disk_ioctl

    DRESULT disk_ioctl 函数用于控制特定于设备的功能和除通用读/写之外的其他功能。

    1. DRESULT disk_ioctl (
    2. BYTE pdrv, /* Physical drive nmuber (0..) */
    3. BYTE cmd, /* Control code */
    4. void *buff /* Buffer to send/receive control data */
    5. )
    6. {
    7. DRESULT res;
    8. if (pdrv == DEV_FLASH) {
    9. switch (cmd) {
    10. /* 扇区数量:3584*4096/1024/1024=14(MB) */
    11. case GET_SECTOR_COUNT:
    12. *(DWORD * )buff = 3584; // 这个值来源于前面512扇区给了文件系统表,后面的才是可以使用的空间 w25q128 4096 扇区 - 512 扇区 = 3584
    13. break;
    14. /* 扇区大小 */
    15. case GET_SECTOR_SIZE :
    16. *(WORD * )buff = 4096;
    17. break;
    18. /* 同时擦除扇区个数 */
    19. case GET_BLOCK_SIZE :
    20. *(DWORD * )buff = 1;
    21. break;
    22. }
    23. res = RES_OK;
    24. }
    25. else
    26. {
    27. res = RES_PARERR;
    28. }
    29. return res;
    30. }

    怎么理解这里的cmd参数呢,有以下命令

     调用disk_ioctl函数以控制设备的特定功能和除通用读/写之外的其他功能。比如通过发送命令来获取该设备的扇区大小、内存大小等相关信息。
    形参:
            第一个形参:要操作的设备号,在本次移植工程中,可选项是SD_CARD(0)或SPI_FLASH(1),只不过后续程序处理部分SD_CARD没进行处理。
            第二个形参:控制命令号。命令号,通过发送命令控制flash;比如查询每个扇区的字节数等等
            第三个形参:数据缓冲区。既有可能输出也有可能输入。因为输入的命令可能会带有参数;发送命令后,需要接收返回来的数据信息。

    返回值:
            RES_OK (0):     此次 操作正常
            RES_ERROR:   有错误产生
            RES_PARERR: 输入的命令或参数是无效的
            RES_NOTRDY: 设备还没初始化

    这里我们只关心

    GET_SECTOR_COUNT

    告诉文件系统,我们的Flash 有多少个可以操作的扇区,这个值来源于前面512扇区给了文件系统,后面的才是可以使用的空间  w25q128 4096 扇区 - 512 扇区 =    3584 个扇区

    GET_SECTOR_SIZE

    获取每一个扇区的大小,一个扇区就是4096 字节。

    GET_BLOCK_SIZE

    以闪存介质扇区为单位的擦除块大小

    该函数的使用必须首先开启宏,使得“FF_USE_MKFS == 1”

    7. 完善系统要求的get_fattime方法

    1. DWORD get_fattime (void)
    2. {
    3. return (DWORD)(2022 - 80) << 25 |
    4. (DWORD)(10 + 1) << 21 |
    5. (DWORD)9 << 16 |
    6. (DWORD)20 << 11 |
    7. (DWORD)20 << 5 |
    8. (DWORD)0 >> 1;
    9. }

    这个方法是我们在对文件操作的时候,会产生一个时间戳,这里我写死了,可以使用RTC完善这里的具体时间参数。

    OK diskio修改完成。


    ffconf.h 的配置

    #define FF_FS_READONLY    0

      要有写功能必须设置为0

    #define FF_USE_MKFS        1

      如果存储芯片不存在文件系统,我们需要对其进行格式化,这里配置为1 使能格式化操作。对应的是f_mkfs()函数

    #define FF_CODE_PAGE    936

      添加中文支持

    #define FF_USE_LFN        2 

       长文件名存储空间设置,缓冲区设置在堆 空间(一般设置_USE_LFN = 2)

     #define FF_VOLUMES        1

    我的代码中只移植了Flash的文件系统,所以只有一个设备DEV_FLASH,所以这里定义为1 ,如果还有其他的设备定义,这可以修改。

    #define FF_MAX_SS        4096

    这里是可以操作的扇区的大小,因为Flash的一个扇区是4096个字节,所以一次可以操作最大设置为4096.这里和 disk_ioctl() 方法里的 GET_SECTOR_SIZE 对应。

    最基本的配置就是这些了。


    应用层调用

    首先挂载文件系统,如果没有文件系统则对底层设备进行格式化,就会创建一个文件系统 ,挂在的“0:”代表DEV_FLASH 定义的数值,: 是必须要写的。

    1. printf("****** 这是一个SPI FLASH 文件系统实验 ******\r\n");
    2. //在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化
    3. //初始化函数调用流程如下
    4. //f_mount()->find_volume()->disk_initialize->SPI_FLASH_Init()
    5. res_flash = f_mount(&fs,"0:",1);
    6. printf("res_flash = %d\r\n",res_flash);
    7. if(res_flash == FR_NO_FILESYSTEM)
    8. {
    9. printf("FLASH还没有文件系统,即将进行格式化...\r\n");
    10. /* 格式化 */
    11. res_flash=f_mkfs("0:",0,work,sizeof(work));
    12. if(res_flash == FR_OK)
    13. {
    14. printf("》FLASH已成功格式化文件系统。\r\n");
    15. /* 格式化后,先取消挂载 */
    16. res_flash = f_mount(NULL,"0:",1);
    17. /* 重新挂载 */
    18. res_flash = f_mount(&fs,"0:",1);
    19. }
    20. else
    21. {
    22. LED_RED = 0;
    23. printf("《《格式化失败。》》\r\n");
    24. while(1);
    25. }
    26. }
    27. else if(res_flash!=FR_OK)
    28. {
    29. printf("!!外部Flash挂载文件系统失败。(%d)\r\n",res_flash);
    30. printf("!!可能原因:SPI Flash初始化不成功。\r\n");
    31. while(1);
    32. }
    33. else
    34. {
    35. printf("》文件系统挂载成功,可以进行读写测试\r\n");
    36. }

     写操作,这里使用了长文件名,并且文件名是中文。并且开启宏 FF_FS_READONLY == 0 

    1. /*------------------- 文件系统测试:写测试 --------------------------*/
    2. printf("\r\n****** 即将进行文件写入测试... ******\r\n");
    3. res_flash = f_open(&fnew, "0:新建文本文档abc.txt",FA_CREATE_ALWAYS | FA_WRITE);
    4. if(res_flash == FR_OK )
    5. {
    6. printf("》打开/创建新建文本文档abc.txt文件成功,向文件写入数据。\r\n");
    7. /* 将指定存储区内容写入到文件内 */
    8. res_flash=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);
    9. if(res_flash == FR_OK)
    10. {
    11. printf("文件写入成功,写入字节数据:%d\r\n",fnum);
    12. delay_ms(1);
    13. printf("向文件写入的数据为:\r\n%s\r\n",WriteBuffer);
    14. delay_ms(1);
    15. }
    16. else
    17. {
    18. printf("!!文件写入失败:(%d)\n",res_flash);
    19. }
    20. /* 不再读写,关闭文件 */
    21. f_close(&fnew);
    22. }
    23. else
    24. {
    25. LED_RED = 0;
    26. printf("!!打开/创建文件失败。\r\n");
    27. }

     文件读测试。

    1. /*------------------- 文件系统测试:读测试 --------------------------*/
    2. printf("****** 即将进行文件读取测试... ******\r\n");
    3. res_flash = f_open(&fnew, "0:新建文本文档abc.txt",FA_OPEN_EXISTING | FA_READ);
    4. if(res_flash == FR_OK)
    5. {
    6. LED_GRREN = 0;
    7. printf("》打开文件成功。\r\n");
    8. res_flash = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
    9. if(res_flash==FR_OK)
    10. {
    11. printf(" 文件读取成功,读到字节数据:%d\r\n",fnum);
    12. printf(" 读取得的文件数据为:\r\n %s \r\n", ReadBuffer);
    13. }
    14. else
    15. {
    16. printf("!!文件读取失败:(%d)\r\n",res_flash);
    17. }
    18. }
    19. else
    20. {
    21. LED_RED = 0;
    22. printf("!!打开文件失败(%d)。\r\n",res_flash);
    23. }
    24. /* 不再读写,关闭文件 */
    25. f_close(&fnew);
    1. /* 不再使用文件系统,取消挂载文件系统 */
    2. f_mount(NULL,"0:",1);

    第一个参数NULL,代表取消挂载文件系统。
    第二个参数“0:”代表取消挂载的设备
    第三个参数 1 立即取消挂载

    验证结果 

    备注,以上是在原子F103精英板子上进行测试

    参考代码

    参考代码地址

    通过USB验证Flash文件

  • 相关阅读:
    caffe安装探索整理
    Kali 基础命令(一)
    985 博士真的会舍弃华为年薪接近 100 万 offer,去选择年薪 20 万的公务员吗?
    信息熵计算及代码
    wayland(xdg_wm_base) + egl + opengles 使用 Assimp 加载3D model 最简实例(十三)
    Qt应用开发(基础篇)——工具按钮类 QToolButton
    使用MySQL,请用好 JSON 这张牌
    MySQL中的事务基础
    jar包打包成exe安装包
    多维下numpy实现torch下的log_softmax
  • 原文地址:https://blog.csdn.net/UserNamezhangxi/article/details/127817695