zoukankan      html  css  js  c++  java
  • stm32--FatFs移植(SPIFlash)

    前言

    1. 硬件:
      1. 单片机:stm32f072CB,sram大小16k。(其他单片机只要sram>8k即可通用)
      2. SPIFlash:W25Q128FV,16Mbyte,单次擦除最小4k。
    2. 程序使用Keil编译器,C99标准。
    3. 程序已经全部完成并测试通过,目前没出现明显问题。
    4. 程序使用的FatFs库版本:R0.13b。下文所有内容仅保证在此版本可行。

    添加文件

    1. 获取FatFs库(官网
    2. 将source文件夹全部复制到目标工程中
    3. 添加所有.c文件到工程中,添加相关路径

    移植修改

    需要修改的文件:

    1. integer.h:修改各种整型的宏定义(注:C99--long long对应64位整型)
    2. ffconf.h:修改各种设置:(全部宏定义意义见官网
      1. FF_USE_STRFUNC设为1:开启字符串功能
      2. FF_USE_MKFS设为1:开启格式化功能
      3. FF_CODE_PAGE设为936:简体中文
      4. FF_MIN_SS、FF_MAX_SS设为4096:扇区大小4k
      5. FF_FS_TINY设为1:文件对象(FIL)不再包括数据缓冲区,而是使用FatFs中的公用缓冲区,适用于RAM偏小的情况。
      6. FF_FS_NORTC设为1:禁用RTC(时间戳)功能,因为stm32不具备获取时间的功能
    3. diskio.c:修改各磁盘IO层操作函数
      1. 修改磁盘设备定义:#define DEV_SPI 0
      2. 修改各函数中case DEV_RAM的操作:stat = STA_NOINIT; 或res = RES_PARERR;
      3. 修改各函数中case DEV_SPI的操作:指向spi_disk.c中的各执行函数
    4. spi_disk.c:自定义文件,是diskio.c中各函数指向的执行函数
      1. 定义静态全局变量_s_SPI_Init_OK,用于指示当前磁盘初始化状态
      2. SPI_disk_status函数:获取驱动器状态。_s_SPI_Init_OK为0时返回STA_NOINIT。
      3. SPI_disk_initialize函数:驱动器初始化。执行SPIFlash初始化函数,执行完毕后将_s_SPI_Init_OK置1。
      4. SPI_disk_read函数:读磁盘驱动器。进行异常处理后,将所有数据读到指定的指针内。
        • 1 DRESULT SPI_disk_read(BYTE *buff, DWORD sector, UINT count) {
          2   if(sector > SEC_MAX || sector + count - 1 > SEC_MAX) return RES_PARERR;
          3   if(CS_STATUS() == Bit_RESET) return RES_NOTRDY;
          4   
          5   if(SSTF016B_RD(sector*SEC_SIZE, SEC_SIZE*count, buff) == ERR) {
          6     return RES_ERROR;
          7   }
          8   return RES_OK;
          9 }
          SPI_disk_read
      5. SPI_disk_write函数:写磁盘驱动器。进行异常处理后,将所有数据写入指定的扇区内。
        • 注意事项:在写完之后,有一个延时20ms的动作。这是因为:FatFs写页表和目录表时,如果写完SPIFlash不加延时,就会写入不成功。这导致的后果是:对页表的修改无法生效。也就是说,对已有文件的数据修改(不改变文件大小)会生效,但新增文件、删除文件不会生效。具体原理暂时不明。
        •  1 DRESULT SPI_disk_write(const BYTE *buff, DWORD sector, UINT count) {
           2   if(sector > SEC_MAX || sector + count - 1 > SEC_MAX) return RES_PARERR;
           3   if(CS_STATUS() == Bit_RESET) return RES_NOTRDY;
           4 
           5   if(SSTF016B_Erase(sector, sector + count - 1) == ERR) {
           6     return RES_ERROR;
           7   }
           8   if(SSTF016B_WR(sector*SEC_SIZE, buff, SEC_SIZE*count) == ERR){
           9     return RES_ERROR;
          10   }
          11   sys_Delay_Ms(20);
          12   return RES_OK;
          13 }
          SPI_disk_write
      6. SPI_disk_ioctl函数:执行ioctl命令。因为没有开启强制擦除的功能,强制擦除指令不执行任何操作。
        • 注意事项:GET_BLOCK_SIZE指令要获取的是块大小(一次擦除的扇区数量),是以扇区为单位的(不是以字节为单位),必须是扇区的2的幂倍(1,2,4,8,...)。这里单扇区设为4k,实际上SPIFlash单次擦除允许的最小也是4k,所以块大小设为1。
        •  1 DRESULT SPI_disk_ioctl(BYTE cmd, void *buff) {
           2   DWORD *pdword = NULL;
           3   WORD  *pword = NULL;
           4   
           5   switch(cmd) {
           6     case CTRL_SYNC://确保写入操作已完成
           7     return RES_OK;
           8     
           9     case GET_SECTOR_COUNT://获取扇区数量
          10       pdword = (DWORD *)buff;
          11       *pdword = SEC_MAX + 1;
          12     return RES_OK;
          13     
          14     case GET_SECTOR_SIZE://获取单个扇区大小
          15       pword = (WORD *)buff;
          16       *pword = SEC_SIZE;
          17     return RES_OK;
          18     
          19     case GET_BLOCK_SIZE://获取擦除块大小(以扇区为单位)
          20       pdword = (DWORD *)buff;
          21       *pdword = 1;
          22     return RES_OK;
          23     
          24     case CTRL_TRIM://强制擦除
          25     return RES_PARERR;
          26   }
          27   return RES_PARERR;
          28 }
          SPI_disk_ioctl
    5. user_fatfs_app.c:自定义文件,定义fatfs的应用函数。
      • FatFs的基础知识
        1. FAT16的结构(参见FAT16文件系统之总结构分析(一)):
          1. rsv:系统保留区(0扇区的DBR,可能存在的分区表,以及其他保留扇区),位于第0--x扇区。
          2. fat:文件页表区(有时会有FAT2作为FAT的备份),位于x+1--y扇区。
          3. dir:文件目录表区,位于y+1--z扇区。
          4. data:数据区,位于z+1--末扇区。
        2. FatFs的格式化(f_mkfs):
          1. f_mkfs的第二个参数:FM_FAT、FM_FAT32、FM_EXFAT、FM_ANY,这四个选项是向下兼容的。也就是说,当你选择FM_FAT时,只可能格式化为FAT12或者FAT16(由扇区数量和簇大小决定);当你选择FM_FAT32时,如果扇区数量和簇大小足以格式化为FAT32,最终就会格式化为FAT32,否则就会向下格式化为FAT16或者FAT12。
          2. f_mkfs的第二个参数:FM_SFD,是格式化为超级软盘格式:如果不选择此选项,rsv区会占用64个扇区(DBR、分区表等);如果选择了此选项,rsv区会占用1个扇区(仅含DBR)。这里如果不设置FM_SFD,减去rsv、fat、dir区后的data区簇数量低于MAX_FAT12(0xFF5),系统格式化为FAT12;所以要设置FM_SFD,以格式化为FAT16。
          3. f_mkfs的第三个参数:可限定簇大小,必须是扇区的2的幂倍(1,2,4,8,...)【注意,BLOCK_SIZE是以扇区为单位,簇大小是以字节为单位。比如块和簇同样设为一个扇区大小,块设为了1,簇设为了4096】。如果设为0,程序就会根据卷数量来分配簇大小。这里设为4096,否则程序根据卷数会将簇设为2扇区,簇数量只能格式化为FAT12。
          4. f_mkfs函数只会写入FAT16的rsv、fat、dir区,对data区不进行任何操作。
          5. f_mkfs函数需要至少一个扇区大小的工作缓冲区。这个缓冲区可以在执行格式化指令的前一句定义,而不是定义为全局数组,如此可以省下4k的ram。局部变量是存储在栈区中的,栈区一般不会很大;而且栈区同样要占用ram,如果把栈区定义得很大,同样是对ram的浪费。所以,还是只能把这个缓冲区定义为全部变量。
        3. FatFs的f_open和f_close函数:
          1. f_open的第三参数:FA_OPEN_ALWAYS--存在则打开、不存在则创建;FA_OPEN_APPEND--同上,但指针指向文件尾;FA_WRITE--要写入必须带此参数;FA_READ--要读取必须带此参数。
          2. 判断f_open操作是打开还是新建的方法:f_size文件对象,返回0则为新建,否则为打开。
          3. 执行写入操作后,必须执行f_close或f_sync函数,这是执行刷新文件页表和目录表的操作。如果不执行此操作而断电,下次上电后文件系统会出错。
        4. FatFs判断文件系统是否存在的方法:f_getfree,返回FR_OK就没有问题。
      • 注意事项:
        1. 在单个扇区设为4k时,创建文件系统需要4k的ram(FATFS对象),每一个文件对象需要4k的ram(FIL对象)。当硬件sram不大时,应仅在需要时创建文件对象并使用、使用完毕马上f_close丢弃文件对象;或者启用FF_FS_TINY选项,文件对象不含数据缓冲区。
        2. 每次执行读/写操作后,文件指针都会指向之前操作的结尾处。可通过f_lseek函数移动文件指针。用f_lseek移动有三个常用的方法:移动至从头开始的第x位--直接传入参数x;移动至末尾前的x位:传入参数f_size(fp)-x;前移/后移x位:传入参数f_tell(fp)±x。
      •   1 #include "user_fatfs_app.h"
          2 
          3 extern uint8_t g_User_Data[sizeof(t_g_Statistical_Data)];
          4 extern t_g_Statistical_Data *pg_User_Data;
          5 extern uint8_t g_History_Data[sizeof(t_g_History_Data)];
          6 extern t_g_History_Data *pg_History_Data;
          7 
          8 FATFS g_Fatfs;
          9 FATFS *fs = &g_Fatfs;
         10 BYTE work_buffer[FF_MAX_SS];
         11 
         12 /******************************************************************************
         13 ** 函数名称: fatfs_Init
         14 ** 功能描述: fatfs初始化
         15 ** 入口参数: 无
         16 ** 返 回 值: 无
         17 **
         18 ** 作 者: 
         19 ** 日 期: 
         20 **-----------------------------------------------------------------------------
         21 ******************************************************************************/
         22 void fatfs_Init(void) {
         23   DWORD num;
         24   
         25   f_mount(&g_Fatfs, "", 0);
         26   FRESULT result = f_getfree("", &num, &fs);
         27   if(result != FR_OK) {
         28     result = f_mkfs("", FM_FAT+FM_SFD, FF_MAX_SS, work_buffer, FF_MAX_SS);
         29   }
         30   
         31   memset(g_User_Data, 0, sizeof(g_User_Data));
         32   memset(g_History_Data, 0, sizeof(g_History_Data));
         33   fatfs_Get_Statistical_Data();
         34 }
         35 
         36 /******************************************************************************
         37 ** 函数名称: fatfs_Get_Statistical_Data
         38 ** 功能描述: 获取统计数据
         39 ** 入口参数: 无
         40 ** 返 回 值: 获取结果:-1--打开文件失败;0--读取成功;>0--读取错误
         41 **
         42 ** 作 者: 
         43 ** 日 期: 
         44 **-----------------------------------------------------------------------------
         45 ******************************************************************************/
         46 int8_t fatfs_Get_Statistical_Data(void) {
         47   FIL statistical_data;
         48   UINT len = 0;
         49   int8_t result = 0;
         50   
         51   result = f_open(&statistical_data, "0:statdat.bin", FA_OPEN_ALWAYS | FA_WRITE | FA_READ);//指针指向文件头
         52   if(result == FR_OK) {
         53     uint16_t size = f_size(&statistical_data);
         54     if(size < sizeof(t_g_Statistical_Data)) { //初次创建
         55       pg_User_Data->year  = 2018;
         56       pg_User_Data->month = 7;
         57       pg_User_Data->day   = 1;
         58       pg_User_Data->hour  = 12;
         59       pg_User_Data->min   = 0;
         60       pg_User_Data->sec   = 0;
         61       f_write(&statistical_data, g_User_Data, sizeof(t_g_Statistical_Data), &len);
         62       f_lseek(&statistical_data, 0);
         63     }
         64     f_read(&statistical_data, g_User_Data, sizeof(t_g_Statistical_Data), &len);
         65   } else {
         66     result = -1;
         67   }
         68   result = f_close(&statistical_data);
         69 //  free(&statistical_data);
         70   return result;
         71 }
         72 
         73 
         74 /******************************************************************************
         75 ** 函数名称: fatfs_Update_Statistical_Data
         76 ** 功能描述: 更新统计数据
         77 ** 入口参数: 无
         78 ** 返 回 值: 更新结果:-1--打开文件失败;0--写入成功;>0--写入错误
         79 **
         80 ** 作 者: 
         81 ** 日 期: 
         82 **-----------------------------------------------------------------------------
         83 ******************************************************************************/
         84 int8_t fatfs_Update_Statistical_Data(void) {
         85   FIL statistical_data;
         86   UINT len = 0;
         87   int8_t result = 0;
         88   
         89   result = f_open(&statistical_data, "0:statdat.bin", FA_OPEN_EXISTING | FA_WRITE);//指针指向文件头
         90   if(result == FR_OK) {
         91     result = f_write(&statistical_data, g_User_Data, sizeof(t_g_Statistical_Data), &len);
         92   } else {
         93     result = -1;
         94   }
         95   f_close(&statistical_data);
         96 //  free(&statistical_data);
         97   
         98   return result;
         99 }
        100 
        101 
        102 /******************************************************************************
        103 ** 函数名称: fatfs_Add_Historical_Data
        104 ** 功能描述: 添加历史记录
        105 ** 入口参数: 无
        106 ** 返 回 值: 添加结果:-2--打开文件错误;-1--写入错误;>0--更新成功
        107 **
        108 ** 作 者: 
        109 ** 日 期: 
        110 **-----------------------------------------------------------------------------
        111 ******************************************************************************/
        112 int8_t fatfs_Add_Historical_Data(void) {
        113   FIL historical_data;
        114   char node_type[2][7] = {"Zigbee", "LoRa"};
        115   char test_result[2][8] = {"Fail", "Success"};
        116   int8_t result = 0;
        117   
        118   result = f_open(&historical_data,  "0:histdat.csv",  FA_OPEN_APPEND | FA_WRITE);//指针指向文件尾
        119   if(result == FR_OK) {
        120     result = f_printf(&historical_data, 
        121              "%u,%s,%s,#%08x%08x%08x,%04u/%02u/%02u,%02u:%02u:%02u
        ", 
        122              pg_History_Data->index, node_type[pg_History_Data->type], 
        123              test_result[pg_History_Data->result], pg_History_Data->id[0], 
        124              pg_History_Data->id[1], pg_History_Data->id[2], pg_History_Data->year,
        125              pg_History_Data->month, pg_History_Data->day, pg_History_Data->hour, 
        126              pg_History_Data->min, pg_History_Data->sec);
        127   } else {
        128     result = -2;
        129   }
        130   f_close(&historical_data);
        131 //  free(&historical_data);
        132   
        133   return result;
        134 }
        user_fatfs_app.c

    待优化的问题:

    1. 文件页表区、目录表区对应扇区的擦写次数必然远大于数据区,等到文件页表区、目录表区擦写次数超限后,数据区仍能继续擦写很多次。考虑加入平衡擦写功能。

    调试修改

    详见我的另一篇博客《stm32--FatFs调试过程(SPIFlash)》

    后续优化

    1. 将一个文件设为隐藏,以免使用USB功能时被修改:
      1. FF_USE_CHMOD设为1:开启元数据控制功能(允许更改文件/目录的属性、时间戳)
      2. 通过f_chmod("0:statdat.bin", AM_HID, AM_HID); 将此文件设为隐藏
      3. 第二个参数:要设置的属性,对应第三个参数中的属性。 
      4. 第三个参数:要变更的属性。如果是第二个参数中存在的属性,就设置这个属性;如果是第二个参数中不存在的属性,就清除这个属性。
    2. 添加文件时间戳功能,创建/修改文件时记录操作时间:
      1. FF_FS_NORTC设为0:开启文件时间戳功能
      2. 在diskio.c中添加函数get_fattime,用于获取当前RTC时间
      3. 返回值是32位无符号数,31-25位为当前年份与1980的差值;24-21位为月;20-16为日;15-11为时;10-5为分;4-0为秒除以2。
      4.  1 DWORD get_fattime (void) {
         2   extern t_g_Statistical_Data *pg_User_Data;
         3   DWORD fat_time = 0;
         4   
         5   fat_time += (pg_User_Data->year - 1980) << 25;
         6   fat_time += pg_User_Data->month << 21;
         7   fat_time += pg_User_Data->day << 16;
         8   fat_time += pg_User_Data->hour << 11;
         9   fat_time += pg_User_Data->min << 5;
        10   fat_time += pg_User_Data->sec / 2;
        11   
        12   return fat_time;
        13 }
        get_fattime
      5. 在diskio.h中添加get_fattime函数声明
  • 相关阅读:
    探讨SQL Server并发处理存在就更新七种解决方案
    集合随机打乱
    订单并发5000的排队机制
    10款面向HTML5 画布(Canvas)的JavaScript库
    抽象类和接口的区别以及使用场景(记)
    常用的正则表达式
    webview加载url出现空白页面,有些页面没问题
    SQL常用语句
    Android Studio 简单介绍和使用问题小结
    Android中获取应用程序(包)的信息-----PackageManager的使用(一)
  • 原文地址:https://www.cnblogs.com/cage666/p/9178326.html
Copyright © 2011-2022 走看看