精华内容
下载资源
问答
  • 第25章 串行FLASH文件系统FatFs

    千次阅读 2018-08-24 13:55:05
    25.1 文件系统 即使读者可能不了解文件系统,读者也一定对“文件”这个概念十分熟悉。数据在PC上是以文件的形式储存在磁盘中的,这些数据的形式一般为ASCII码或二进制形式。在上一章我们已经写好了QSPI Flash芯片的...

    25.1 文件系统

    即使读者可能不了解文件系统,读者也一定对“文件”这个概念十分熟悉。数据在PC上是以文件的形式储存在磁盘中的,这些数据的形式一般为ASCII码或二进制形式。在上一章我们已经写好了QSPI Flash芯片的驱动函数,我们可以非常方便的在QSPI Flash芯片上读写数据。如需要记录本书的书名“零死角玩转STM32-F7系列”,可以把这些文字转化成ASCII码,存储在数组中,然后调用QSPI_FLASH_BufferWrite函数,把数组内容写入到QSPI Flash芯片的指定地址上,在需要的时候从该地址把数据读取出来,再对读出来的数据以ASCII码的格式进行解读。

    但是,这样直接存储数据会带来极大的不便,如难以记录有效数据的位置,难以确定存储介质的剩余空间,以及应以何种格式来解读数据。就如同一个巨大的图书馆无人管理,杂乱无章地存放着各种书籍,难以查找所需的文档。想象一下图书馆的采购人员购书后,把书籍往馆内一扔,拍拍屁股走人,当有人来借阅某本书的时候,就不得不一本本地查找。这样直接存储数据的方式对于小容量的存储介质如EEPROM还可以接受,但对于QSPI Flash芯片或者SD卡之类的大容量设备,我们需要一种高效的方式来管理它的存储内容。

    这些管理方式即为文件系统,它是为了存储和管理数据,而在存储介质建立的一种组织结构,这些结构包括操作系统引导区、目录和文件。常见的windows下的文件系统格式包括FAT32、NTFS、exFAT。在使用文件系统前,要先对存储介质进行格式化。格式化先擦除原来内容,在存储介质上新建一个文件分配表和目录。这样,文件系统就可以记录数据存放的物理地址,剩余空间。

    使用文件系统时, 数据都以文件的形式存储。写入新文件时,先在目录中创建一个文件索引,它指示了文件存放的物理地址,再把数据存储到该地址中。当需要读取数据时,可以从目录中找到该文件的索引,进而在相应的地址中读取出数据。具体还涉及到逻辑地址、簇大小、不连续存储等一系列辅助结构或处理过程。

    文件系统的存在使我们在存取数据时,不再是简单地向某物理地址直接读写,而是要遵循它的读写格式。如经过逻辑转换,一个完整的文件可能被分开成多段存储到不连续的物理地址,使用目录或链表的方式来获知下一段的位置。

    上一章的SPI Flash芯片驱动只完成了向物理地址写入数据的工作,而根据文件系统格式的逻辑转换部分则需要额外的代码来完成。实质上,这个逻辑转换部分可以理解为当我们需要写入一段数据时,由它来求解向什么物理地址写入数据、以什么格式写入及写入一些原始数据以外的信息(如目录)。这个逻辑转换部分代码我们也习惯称之为文件系统。

    25.2  FatFs文件系统简介

    上面提到的逻辑转换部分代码(文件系统)即为本章的要点,文件系统庞大而复杂,它需要根据应用的文件系统格式而编写,而且一般与驱动层分离开来,很方便移植,所以工程应用中一般是移植现成的文件系统源码。

    FatFs是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由AISI C语言编写并且完全独立于底层的I/O介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如8051、PIC、AVR、SH、Z80、H8、ARM等。FatFs支持FAT12、FAT16、FAT32等格式,所以我们利用前面写好的QSPI Flash芯片驱动,把FatFs文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对QSPI Flash芯片以“文件”格式进行读写操作了。

    FatFs文件系统的源码可以从fatfs官网下载:

    http://elm-chan.org/fsw/ff/00index_e.html

    25.2.1  FatFs的目录结构

    在移植FatFs文件系统到开发板之前,我们先要到FatFs的官网获取源码,最新版本为R0.11a,官网有对FatFs做详细的介绍,有兴趣可以了解。解压之后可看到里面有  doc 和 src 这两个文件夹,见图 251。doc 文件夹里面是一些使用帮助文档; src 才是FatFs文件系统的源码。

     

    图 25-1  FatFs文件目录

    25.2.2 FatFs帮助文档

    打开  doc 文件夹,可看到如图 25-2的文件目录:

     

    图 25-2 doc文件夹的文件目录

    其中 en 和 ja 这两个文件夹里面是编译好的html文档,讲的是FATFS里面各个函数的使用方法,这些函数都是封装得非常好的函数,利用这些函数我们就可以操作SPI Flash芯片。有关具体的函数我们在用到的时候再讲解。这两个文件夹的唯一区别就是 en 文件夹下的文档是英文的,ja 文件夹下的是日文的。img文件夹包含en和ja文件夹下文件需要用到的图片,还有四个名为app.c文件,内容都是FatFs具体应用例程。00index_e.html和00index_j.html是一些关于FATFS的简介,至于另外两个文件可以不看。

    25.2.3  FATFS源码

    打开 src 文件夹,可看到如图 25-3的文件目录:

     

    图 25-3 src文件夹的文件目录

    option 文件夹下是一些可选的外部c文件,包含了多语言支持需要用到的文件和转换函数。

    diskio.c文件是FatFs移植最关键的文件,它为文件系统提供了最底层的访问QSPI Flash芯片的方法,FatFs有且仅有它需要用到与QSPI Flash芯片相关的函数。diskio.h定义了FatFs用到的宏,以及diskio.c文件内与底层硬件接口相关的函数声明。

    00history.txt介绍了FatFs的版本更新情况。

    00readme.txt说明了当前目录下 diskio.c 、diskio.h、ff.c、ff.h、integer.h的功能。

    src文件夹下的源码文件功能简介如下:

    1. integer.h:文件中包含了一些数值类型定义。
    2. diskio.c:包含底层存储介质的操作函数,这些函数需要用户自己实现,主要添加底层驱动函数。
    3. ff.c: FatFs核心文件,文件管理的实现方法。该文件独立于底层介质操作文件的函数,利用这些函数实现文件的读写。 
    4. cc936.c:本文件在option目录下,是简体中文支持所需要添加的文件,包含了简体中文的GBK和Unicode相互转换功能函数。
    5. ffconf.h:这个头文件包含了对FatFs功能配置的宏定义,通过修改这些宏定义就可以裁剪FatFs的功能。如需要支持简体中文,需要把ffconf.h中的_CODE_PAGE 的宏改成936并把上面的cc936.c文件加入到工程之中。

    建议阅读这些源码的顺序为:integer.h --> diskio.c --> ff.c 。

    阅读文件系统源码ff.c文件需要一定的功底,建议读者先阅读FAT32的文件格式,再去分析ff.c文件。若仅为使用文件系统,则只需要理解integer.h及diskio.c文件并会调用ff.c文件中的函数就可以了。本章主要讲解如何把FATFS文件系统移植到开发板上,并编写一个简单读写操作范例。

    25.3 FatFs文件系统移植实验

    25.5.1 FatFs程序结构图

    移植FatFs之前我们先通过FatFs的程序结构图了解FatFs在程序中的关系网络,见图 25-4。

     

    图 25-4 FatFs程序结构图

    用户应用程序需要由用户编写,想实现什么功能就编写什么的程序,一般我们只用到f_mount()、f_open()、f_write()、f_read()就可以实现文件的读写操作。

    FatFs组件是FatFs的主体,文件都在源码src文件夹中,其中ff.c、ff.h、integer.h以及diskio.h四个文件我们不需要改动,只需要修改ffconf.h和diskio.c两个文件。

    底层设备输入输出要求实现存储设备的读写操作函数、存储设备信息获取函数等等。我们使用QSPI Flash芯片作为物理设备,在上一章节已经编写好了QSPI Flash芯片的驱动程序,这里我们就直接使用。

    25.3.2 硬件设计

    FatFs属于软件组件,不需要附带其他硬件电路。我们使用QSPI Flash芯片作为物理存储设备,其硬件电路在上一章已经做了分析,这里就直接使用。

    25.3.3 FatFs移植步骤

    上一章我们已经实现了QSPI Flash芯片驱动程序,并实现了读写测试,为移植FatFs方便,我们直接拷贝一份工程,我们在工程基础上添加FatFs组件,并修改main函数的用户程序即可。

    1)先拷贝一份QSPI Flash芯片测试的工程文件(整个文件夹),并修改文件夹名为“QSPI—FatFs文件系统”。将FatFs源码中的src文件夹整个文件夹拷贝一份至“QSPI—FatFs文件系统\USER\”文件夹下并修改名为“FATFS”,见图 25-5。

    图 25-5 拷贝FatFs源码到工程

    2)使用KEIL软件打开工程文件(..\QSPI—FatFs文件系统\Project\RVMDK(uv5)\ BH-F767.uvprojx),并将FatFs组件文件添加到工程中,需要添加有ff.c、diskio.c和cc936.c三个文件,见图 25-6。

     

    图 25-6  添加FatFS文件到工程

    3)添加FATFS文件夹到工程的include选项中。打开工程选项对话框,选择“C/C++”选项下的“Include Paths”项目,在弹出路径设置对话框中选择添加“FATFS”文件夹,见图 25-7。

     

    图 25-7 添加FATFS路径到工程选项

    4)如果现在编译工程,可以发现有两个错误,一个是来自diskio.c文件,提示有一些头文件没找,diskio.c文件内容是与底层设备输入输出接口函数文件,不同硬件设计驱动就不同,需要的文件也不同;另外一个错误来自cc936.c文件,提示该文件不是工程所必需的,这是因为FatFs默认使用日语,我们想要支持简体中文需要修改FatFs的配置,即修改ffconf.h文件。至此,将FatFs添加到工程的框架已经操作完成,接下来要做的就是修改diskio.c文件和ffconf.h文件。

    25.3.4 FatFs底层设备驱动函数

    FatFs文件系统与底层介质的驱动分离开来,对底层介质的操作都要交给用户去实现,它仅仅是提供了一个函数接口而已。表 25-1为FatFs移植时用户必须支持的函数。通过表 25-1我们可以清晰知道很多函数是在一定条件下才需要添加的,只有前三个函数是必须添加的。我们完全可以根据实际需求选择实现用到的函数。

    前三个函数是实现读文件最基本需求。接下来三个函数是实现创建文件、修改文件需要的。为实现格式化功能,需要在disk_ioctl添加两个获取物理设备信息选项。我们一般只有实现前面六个函数就可以了,已经足够满足大部分功能。

    为支持简体中文长文件名称需要添加ff_convert和ff_wtoupper函数,实际这两个已经在cc936.c文件中实现了,我们只要直接把cc936.c文件添加到工程中就可以了。

    后面六个函数一般都不用。如真有需要可以参考syscall.c文件(src\option文件夹内)。

    表 25-1 FatFs移植需要用户支持函数

    函数

    条件(ffconf.h)

    备注

    disk_status
    disk_initialize
    disk_read

    总是需要

    底层设备驱动函数

    disk_write
    get_fattime
    disk_ioctl (CTRL_SYNC)

    _FS_READONLY == 0

    disk_ioctl (GET_SECTOR_COUNT)
    disk_ioctl (GET_BLOCK_SIZE)

    _USE_MKFS == 1

    disk_ioctl (GET_SECTOR_SIZE)

    _MAX_SS != _MIN_SS

    disk_ioctl (CTRL_TRIM)

    _USE_TRIM == 1

    ff_convert
    ff_wtoupper

    _USE_LFN != 0

    Unicode支持,为支持简体中文,添加cc936.c到工程即可

    ff_cre_syncobj
    ff_del_syncobj
    ff_req_grant
    ff_rel_grant

    _FS_REENTRANT == 1

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

    ff_mem_alloc
    ff_mem_free

    _USE_LFN == 3

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

    底层设备驱动函数是存放在diskio.c文件,我们的目的就是把diskio.c中的函数接口与QSPI Flash芯片驱动连接起来。总共有五个函数,分别为设备状态获取(disk_status)、设备初始化(disk_initialize)、扇区读取(disk_read)、扇区写入(disk_write)、其他控制(disk_ioctl)。

    接下来,我们对每个函数结合QSPI Flash芯片驱动做详细讲解。

    设备状态获取

    代码清单 25-1设备状态获取

    1 DSTATUS TM_FATFS_FLASH_SPI_disk_status(BYTE lun)

     2 {

     3     FLASH_DEBUG_FUNC();

     4     if (sFLASH_ID == QSPI_FLASH_ReadID()) {   /*检测FLASH是否正常工作*/

     5         return TM_FATFS_FLASH_SPI_Stat &= ~STA_NOINIT;  /* Clear STA_NOINIT flag */

     6     } else {

     7         return TM_FATFS_FLASH_SPI_Stat |= STA_NOINIT;

     8     }

     9 }  

    TM_FATFS_FLASH_SPI_disk_status函数只有一个参数lun,没有使用。对于QSPI Flash芯片,我们直接调用在QSPI_FLASH_ReadID()获取设备ID,然后判断是否正确,如果正确,函数返回正常标准;如果错误,函数返回异常标志。

    设备初始化

    代码清单 25-2 设备初始化

    1 DSTATUS TM_FATFS_FLASH_SPI_disk_initialize(BYTE lun)

     2 {

     3     GPIO_InitTypeDef GPIO_InitStruct;

     4

     5     /* 使能 QSPI 及 GPIO 时钟 */

     6     QSPI_FLASH_CLK_ENABLE();

     7     QSPI_FLASH_CLK_GPIO_ENABLE();

     8     QSPI_FLASH_BK1_IO0_CLK_ENABLE();

     9     QSPI_FLASH_BK1_IO1_CLK_ENABLE();

    10     QSPI_FLASH_BK1_IO2_CLK_ENABLE();

    11     QSPI_FLASH_BK1_IO3_CLK_ENABLE();

    12     QSPI_FLASH_CS_GPIO_CLK_ENABLE();

    13

    14     //设置引脚

    15     /*!< 配置 QSPI_FLASH 引脚: CLK */

    16     GPIO_InitStruct.Pin = QSPI_FLASH_CLK_PIN;

    17     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;

    18     GPIO_InitStruct.Pull = GPIO_NOPULL;

    19     GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;

    20     GPIO_InitStruct.Alternate = QSPI_FLASH_CLK_GPIO_AF;

    21

    22     HAL_GPIO_Init(QSPI_FLASH_CLK_GPIO_PORT, &GPIO_InitStruct);

    23

    24     /*!< 配置 QSPI_FLASH 引脚: IO0 */

    25     GPIO_InitStruct.Pin = QSPI_FLASH_BK1_IO0_PIN;

    26     GPIO_InitStruct.Alternate = QSPI_FLASH_BK1_IO0_AF;

    27     HAL_GPIO_Init(QSPI_FLASH_BK1_IO0_PORT, &GPIO_InitStruct);

    28

    29     /*!< 配置 QSPI_FLASH 引脚: IO1 */

    30     GPIO_InitStruct.Pin = QSPI_FLASH_BK1_IO1_PIN;

    31     GPIO_InitStruct.Alternate = QSPI_FLASH_BK1_IO1_AF;

    32     HAL_GPIO_Init(QSPI_FLASH_BK1_IO1_PORT, &GPIO_InitStruct);

    33

    34     /*!< 配置 QSPI_FLASH 引脚: IO2 */

    35     GPIO_InitStruct.Pin = QSPI_FLASH_BK1_IO2_PIN;

    36     GPIO_InitStruct.Alternate = QSPI_FLASH_BK1_IO2_AF;

    37     HAL_GPIO_Init(QSPI_FLASH_BK1_IO2_PORT, &GPIO_InitStruct);

    38

    39     /*!< 配置 QSPI_FLASH 引脚: IO3 */

    40     GPIO_InitStruct.Pin = QSPI_FLASH_BK1_IO3_PIN;

    41     GPIO_InitStruct.Alternate = QSPI_FLASH_BK1_IO3_AF;

    42     HAL_GPIO_Init(QSPI_FLASH_BK1_IO3_PORT, &GPIO_InitStruct);

    43

    44     /*!< 配置 SPI_FLASH_SPI 引脚: NCS */

    45     GPIO_InitStruct.Pin = QSPI_FLASH_CS_PIN;

    46     GPIO_InitStruct.Alternate = QSPI_FLASH_CS_GPIO_AF;

    47     HAL_GPIO_Init(QSPI_FLASH_CS_GPIO_PORT, &GPIO_InitStruct);

    48

    49     /* QSPI_FLASH 模式配置 */

    50

    51     hqspi.Instance = QUADSPI;

    52     hqspi.Init.ClockPrescaler = 1;

    53     hqspi.Init.FifoThreshold = 4;

    54     hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;

    55     hqspi.Init.FlashSize = 23;

    56     hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;

    57     hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;

    58     HAL_QSPI_Init(&hqspi);

    59

    60     BSP_QSPI_Init();

    61

    62     return TM_FATFS_FLASH_SPI_disk_status(NULL);

    63

    64 }

    TM_FATFS_FLASH_SPI_disk_initialize函数也是有一个参数lun,没有使用。对于QSPI Flash芯片我们调用HAL_QSPI_Init 函数实现对SPI Flash芯片引脚GPIO初始化配置,调用BSP_QSPI_Init ()函数对通信参数配置。

    最后调用TM_FATFS_FLASH_SPI_disk_status函数获取QSPI Flash芯片状态,并返回状态值。

    读取扇区

    代码清单 25-3 扇区读取

    1 DRESULT TM_FATFS_FLASH_SPI_disk_read(

     2     BYTE lun,//物理扇区,多个设备时用到(0...)

     3     BYTE *buff,//数据缓存区

     4     DWORD sector, //扇区首地址

     5     UINT count)//扇区个数(1..128)

     6 {

     7     FLASH_DEBUG_FUNC();

     8     if ((TM_FATFS_FLASH_SPI_Stat & STA_NOINIT)) {

     9         return RES_NOTRDY;

    10     }

    11     sector+=1536;//扇区偏移,外部Flash文件系统空间放在外部Flash后面6M空间

    12     BSP_QSPI_Read(buff, sector <<12, count<<12);

    13     return RES_OK;

    14 }

    TM_FATFS_FLASH_SPI_disk_read函数有四个形参。lun为设备物理编号。buff是一个BYTE类型指针变量,buff指向用来存放读取到数据的存储区首地址。sector是一个DWORD类型变量,指定要读取数据的扇区首地址。count是一个UINT类型变量,指定扇区数量。

    BYTE类型实际是unsigned char类型,DWORD类型实际是unsigned long类型,UINT类型实际是 unsigned int类型,类型定义在integer.h文件中。

    开发板使用的QSPI Flash芯片型号为W25Q128FV,每个扇区大小为4096个字节(4KB),总共有16M字节空间,为兼容后面实验程序,我们只将后部分10MB空间分配给FatFs使用,前部分6MB空间用于其他实验需要,即FatFs是从6MB空间开始,为实现这个效果需要将所有的读写地址都偏移1536个扇区空间。

    对于QSPI Flash芯片,主要是使用QSPI_FLASH_Read()实现在指定地址读取指定长度的数据,它接收三个参数,第一个参数为指定数据存放地址指针。第二个参数为指定数据读取地址,这里使用左移运算符,左移12位实际是乘以4096,这与每个扇区大小是息息相关的。第三个参数为读取数据个数,也是需要使用左移运算符。

    扇区写入

    代码清单 25-4 扇区输入

    1 DRESULT TM_FATFS_FLASH_SPI_disk_write(

     2     BYTE lun,//物理扇区,多个设备时用到(0...)

     3     const BYTE *buff,//数据缓存区

     4     DWORD sector, //扇区首地址

     5     UINT count)//扇区个数(1..128)

     6 {

     7     uint32_t write_addr;

     8     FLASH_DEBUG_FUNC();

     9     sector+=1536;//扇区偏移,外部Flash文件系统空间放在外部Flash后面4M空间

    10     write_addr = sector<<12;

    11     BSP_QSPI_Erase_Block(write_addr);

    12     BSP_QSPI_Write((uint8_t*)buff,write_addr,4096);

    13     return RES_OK;

    14 }

    TM_FATFS_FLASH_SPI_disk_write函数有四个形参,lun为设备物理编号。buff指向待写入扇区数据的首地址。sector,指定要读取数据的扇区首地址。count指定扇区数量。对于QSPI Flash芯片,在写入数据之前需要先擦除,所以用到扇区擦除函数(BSP_QSPI_Erase_Block)。然后就是在调用数据写入函数(BSP_QSPI_Write)把数据写入到指定位置内。

    其他控制

    代码清单 25-5  其他控制

    1 DRESULT TM_FATFS_FLASH_SPI_disk_ioctl(BYTE lun,BYTE cmd, void *buff)

     2 {

     3

     4     FLASH_DEBUG_FUNC();

     5     switch (cmd) {

     6     case GET_SECTOR_COUNT:

     7         *(DWORD * )buff = 2560; /* 扇区数量:2560*4096/1024/1024=10(MB) */

     8         break;

     9     case GET_SECTOR_SIZE :     /*获取扇区读写的大小(字)*/

    10

    11         *(WORD * )buff = 4096;  /*flash最小写单元为页,256字节,此处取2页为一个读写单位*/

    12         break;

    13     case GET_BLOCK_SIZE :      /* 同时擦除扇区个数(双字) */

    14         *(DWORD * )buff = 1;   /*flash以1个sector为最小擦除单位*/

    15         break;

    16     case CTRL_TRIM:

    17         break;

    18     case CTRL_SYNC :

    19         break;

    20     }

    21     return RES_OK;

    22 }

    TM_FATFS_FLASH_SPI_disk_ioctl函数有三个形参,lun为设备物理编号,cmd为控制指令,包括发出同步信号、获取扇区数目、获取扇区大小、获取擦除块数量等等指令,buff为指令对应的数据指针。

    对于QSPI Flash芯片,为支持FatFs格式化功能,需要用到获取扇区数量(GET_SECTOR_COUNT)指令和获取擦除块数量(GET_BLOCK_SIZE)。另外,SD卡扇区大小为512字节,SPI Flash芯片一般设置扇区大小为4096字节,所以需要用到获取扇区大小(GET_SECTOR_SIZE)指令。

    时间戳获取

    代码清单 25-6  时间戳获取

    1 __weak DWORD get_fattime(void)

     2 {

     3     /* 返回当前时间戳 */

     4     return    ((DWORD)(2015 - 1980) << 25)  /* Year 2015 */

     5               | ((DWORD)1 << 21)        /* Month 1 */

     6               | ((DWORD)1 << 16)        /* Mday 1 */

     7               | ((DWORD)0 << 11)        /* Hour 0 */

     8               | ((DWORD)0 << 5)         /* Min 0 */

     9               | ((DWORD)0 >> 1);        /* Sec 0 */

    10 }

    get_fattime函数用于获取当前时间戳,在ff.c文件中被调用。FatFs在文件创建、被修改时会记录时间,这里我们直接使用赋值方法设定时间戳。为更好的记录时间,可以使用控制器RTC功能,具体要求返回值格式为:

    1. bit31:25 ——从1980至今是多少年,范围是 (0..127) ;
    2. bit24:21 ——月份,范围为 (1..12) ;
    3. bit20:16 ——该月份中的第几日,范围为(1..31) ;
    4. bit15:11——时,范围为 (0..23);
    5.  bit10:5  ——分,范围为 (0..59);
    6.  bit4:0   ——秒/ 2,范围为 (0..29) 。
        1. FatFs功能配置

    ffconf.h文件是FatFs功能配置文件,我们可以对文件内容进行修改,使得FatFs更符合我们的要求。ffconf.h对每个配置选项都做了详细的使用情况说明。下面只列出修改的配置,其他配置采用默认即可。

    代码清单 25-7  FatFs功能配置选项

    1 #define _USE_MKFS   1

     2 #define _CODE_PAGE  936

     3 #define _USE_LFN    2

     4 #define _VOLUMES    3

    5 #define _MIN_SS     512

    6 #define _MAX_SS     4096

    1. _USE_MKFS:格式化功能选择,为使用FatFs格式化功能,需要把它设置为1。
    2. _CODE_PAGE:语言功能选择,并要求把相关语言文件添加到工程宏。为支持简体中文文件名需要使用“936”,正如在图 256的操作,我们已经把cc936.c文件添加到工程中。
    3. _USE_LFN:长文件名支持,默认不支持长文件名,这里配置为2,支持长文件名,并指定使用栈空间为缓冲区。
    4. _VOLUMES:指定物理设备数量,这里设置为3,包括预留SD卡和qSPI Flash芯片。
    5. _MIN_SS 、_MAX_SS:指定扇区大小的最小值和最大值。SD卡扇区大小一般都为512字节,SPI Flash芯片扇区大小一般设置为4096字节,所以需要把_MAX_SS改为4096。
        1. FatFs功能测试

    移植操作到此,就已经把FatFs全部添加到我们的工程了,这时我们编译功能,顺利编译通过,没有错误。接下来,我们就可以使用编写图 25-4中用户应用程序了。

    主要的测试包括格式化测试、文件写入测试和文件读取测试三个部分,主要程序都在main.c文件中实现。

    变量定义

    代码清单 25-8  变量定义

    1 FATFS fs;                         /* FatFs文件系统对象 */

     2 FIL fnew;                         /* 文件对象 */

     3 FRESULT res_flash;                /* 文件操作结果 */

     4 UINT fnum;                        /* 文件成功读写数量 */

     5 BYTE buffer[1024]= {0};           /* 读缓冲区 */

     6 BYTE textFileBuffer[] =           /* 写缓冲区*/

     7     "欢迎使用野火STM32 F429开发板 今天是个好日子,新建文件系统测试文件\r\n";

    FATFS是在ff.h文件定义的一个结构体类型,针对的对象是物理设备,包含了物理设备的物理编号、扇区大小等等信息,一般我们都需要为每个物理设备定义一个FATFS变量。

    FIL也是在ff.h文件定义的一个结构体类型,针对的对象是文件系统内具体的文件,包含了文件很多基本属性,比如文件大小、路径、当前读写地址等等。如果需要在同一时间打开多个文件进行读写,才需要定义多个FIL变量,不然一般定义一个FIL变量即可。

    FRESULT是也在ff.h文件定义的一个枚举类型,作为FatFs函数的返回值类型,主要管理FatFs运行中出现的错误。总共有19种错误类型,包括物理设备读写错误、找不到文件、没有挂载工作空间等等错误。这在实际编程中非常重要,当有错误出现是我们要停止文件读写,通过返回值我们可以快速定位到错误发生的可能地点。如果运行没有错误才返回FR_OK。

    fnum是个32位无符号整形变量,用来记录实际读取或者写入数据的数组。

    buffer和textFileBuffer分别对应读取和写入数据缓存区,都是8位无符号整形数组。

    主函数

    代码清单 25-9 主函数

    1 int main(void)

     2 {

     3     SystemClock_Config();

     4     /* Enable I-Cache */

     5     SCB_EnableICache();

     6     /* Enable D-Cache */

     7     SCB_EnableDCache();

     8     /* 初始化LED */

     9     LED_GPIO_Config();

    10     LED_BLUE;

    11

    12     /* 初始化调试串口,一般为串口1 */

    13     DEBUG_USART_Config();

    14     printf("****** 这是一个SPI FLASH 文件系统实验 ******\r\n");

    15     //链接驱动器,创建盘符

    16     FATFS_LinkDriver(&QSPI_Driver, QSPIPath);

    17     //在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化

    18     res_flash = f_mount(&fs,"0:",1);

    19

    20     /*----------------------- 格式化测试 ---------------------------*/

    21     /* 如果没有文件系统就格式化创建创建文件系统 */

    22     if (res_flash == FR_NO_FILESYSTEM) {

    23         printf("》FLASH还没有文件系统,即将进行格式化...\r\n");

    24         /* 格式化 */

    25         res_flash=f_mkfs("0:",0,0);

    26

    27         if (res_flash == FR_OK) {

    28             printf("》FLASH已成功格式化文件系统。\r\n");

    29             /* 格式化后,先取消挂载 */

    30             res_flash = f_mount(NULL,"0:",1);

    31             /* 重新挂载 */

    32             res_flash = f_mount(&fs,"0:",1);

    33         } else {

    34             LED_RED;

    35             printf("《《格式化失败。》》\r\n");

    36             while (1);

    37         }

    38     } else if (res_flash!=FR_OK) {

    39         printf("!!外部Flash挂载文件系统失败。(%d)\r\n",res_flash);

    40         printf("!!可能原因:SPI Flash初始化不成功。\r\n");

    41         while (1);

    42     } else {

    43         printf("》文件系统挂载成功,可以进行读写测试\r\n");

    44     }

    45

    46     /*----------------------- 文件系统测试:写测试 -----------------------------*/

    47     /* 打开文件,如果文件不存在则创建它 */

    48     printf("\r\n****** 即将进行文件写入测试... ******\r\n");

    49     res_flash = f_open(&fnew, "0:FatFs读写测试文件.txt",FA_CREATE_ALWAYS | FA_WRITE );

    50     if ( res_flash == FR_OK ) {

    51         printf("》打开/创建FatFs读写测试文件.txt文件成功,向文件写入数据。\r\n");

    52         /* 将指定存储区内容写入到文件内 */

    53         res_flash=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);

    54         if (res_flash==FR_OK) {

    55             printf("》文件写入成功,写入字节数据:%d\n",fnum);

    56             printf("》向文件写入的数据为:\r\n%s\r\n",WriteBuffer);

    57         } else {

    58             printf("!!文件写入失败:(%d)\n",res_flash);

    59         }

    60         /* 不再读写,关闭文件 */

    61         f_close(&fnew);

    62     } else {

    63         LED_RED;

    64         printf("!!打开/创建文件失败。\r\n");

    65     }

    66

    67     /*------------------- 文件系统测试:读测试 ------------------------------------*/

    68     printf("****** 即将进行文件读取测试... ******\r\n");

    69     res_flash = f_open(&fnew, "0:FatFs读写测试文件.txt", FA_OPEN_EXISTING | FA_READ);

    70     if (res_flash == FR_OK) {

    71         LED_GREEN;

    72         printf("》打开文件成功。\r\n");

    73         res_flash = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);

    74         if (res_flash==FR_OK) {

    75             printf("》文件读取成功,读到字节数据:%d\r\n",fnum);

    76             printf("》读取得的文件数据为:\r\n%s \r\n", ReadBuffer);

    77         } else {

    78             printf("!!文件读取失败:(%d)\n",res_flash);

    79         }

    80     } else {

    81         LED_RED;

    82         printf("!!打开文件失败。\r\n");

    83     }

    84     /* 不再读写,关闭文件 */

    85     f_close(&fnew);

    86

    87     /* 不再使用文件系统,取消挂载文件系统 */

    88     f_mount(NULL,"0:",1);

    89

    90     /* 操作完成,停机 */

    91     while (1) {

    92     }

    93 }

    首先,初始化系统时钟,RGB彩灯和调试串口,用来指示程序进程。

    FatFs的第一步工作就是链接驱动器,创建盘符然后使用f_mount函数挂载工作区。f_mount函数有三个形参,第一个参数是指向FATFS变量指针,如果赋值为NULL可以取消物理设备挂载。第二个参数为逻辑设备编号,使用设备根路径表示,与物理设备编号挂钩,这里使用“0:”。第三个参数可选0或1,1表示立即挂载,0表示不立即挂载,延迟挂载。 f_mount函数会返回一个FRESULT类型值,指示运行情况。

    如果f_mount函数返回值为FR_NO_FILESYSTEM,说明没有FAT文件系统,比如新出厂的SPI Flash芯片就没有FAT文件系统。我们就必须对物理设备进行格式化处理。使用f_mkfs函数可以实现格式化操作。f_mkfs函数有三个形参,第一个参数为逻辑设备编号;第二参数可选0或者1,0表示设备为一般硬盘,1表示设备为软盘。第三个参数指定扇区大小,如果为0,表示通过代码清单 25-5中TM_FATFS_FLASH_SPI_disk_ioctl函数获取。格式化成功后需要先取消挂载原来设备,再重新挂载设备。

    在设备正常挂载后,就可以进行文件读写操作了。使用文件之前,必须使用f_open函数打开文件,不再使用文件必须使用f_close函数关闭文件,这个跟电脑端操作文件步骤类似。f_open函数有三个形参,第一个参数为文件对象指针。第二参数为目标文件,包含绝对路径的文件名称和后缀名。第三个参数为访问文件模式选择,可以是打开已经存在的文件模式、读模式、写模式、新建模式、总是新建模式等的或运行结果。比如对于写测试,使用FA_CREATE_ALWAYS和FA_WRITE组合模式,就是总是新建文件并进行写模式。

    f_close函数用于不再对文件进行读写操作关闭文件,f_close函数只要一个形参,为文件对象指针。f_close函数运行可以确保缓冲区完全写入到文件内。

    成功打开文件之后就可以使用f_write函数和f_read函数对文件进行写操作和读操作。这两个函数用到的参数是一致的,只不过一个是数据写入,一个是数据读取。f_write函数第一个形参为文件对象指针,使用与f_open函数一致即可。第二个参数为待写入数据的首地址,对于f_read函数就是用来存放读出数据的首地址。第三个参数为写入数据的字节数,对于f_read函数就是欲读取数据的字节数。第四个参数为32位无符号整形指针,这里使用fnum变量地址赋值给它,在运行读写操作函数后,fnum变量指示成功读取或者写入的字节个数。

    最后,不再使用文件系统时,使用f_mount函数取消挂载。

    25.3.7 下载验证

    保证开发板相关硬件连接正确,用USB线连接开发板“USB TO UART”接口跟电脑,在电脑端打开串口调试助手,把编译好的程序下载到开发板。程序开始运行后,RGB彩灯为蓝色,在串口调试助手可看到格式化测试、写文件检测和读文件检测三个过程;最后如果所有读写操作都正常,RGB彩灯会指示为绿色,如果在运行中FatFs出现错误RGB彩灯指示为红色。

    虽然我们通过RGB彩灯指示和串口调试助手信息打印方法来说明FatFs移植成功,并顺利通过测试,但心底总是很踏实,所谓眼见为实,虽然我们创建了“FatFs读写测试文件.txt”这个文件,却完全看不到实体。这个确实是个问题,因为我们这里使用SPI Flash芯片作为物理设备,并不像SD卡那么方便直接用读卡器就可以在电脑端打开验证。另外一个问题,就目前来说,在QSPI Flash芯片上挂载FatFs好像没有实际意义,无法发挥文件系统功能。

    实际上,这里归根到底就是我们目前没办法在电脑端查看QSPI Flash芯片内FatFs的内容,没办法非常方便拷贝、删除文件。我们当然不会做无用功,STM32控制器还有一个硬件资源可以解决上面的问题,就是USB!我们可以通过编程把整个开发板变成一个U盘,而U盘存储空间就是SPI Flash芯片的空间。这样非常方便实现文件读写。至于USB内容将在USB相关章节讲解。

    25.4 FatFs功能使用实验

    上个实验我们实现了FatFs的格式化、读文件和写文件功能,这个已经满足很多部分的运用需要。有时,我们需要更多的文件操作功能,FatFs还是提供了不少的功能的,比如设备存储空间信息获取、读写文件指针定位、创建目录、文件移动和重命名、文件或目录信息获取等等功能。我们接下来这个实验内容就是展示FatFs众多功能,提供一个很好了范例,以后有用到相关内容,参考使用非常方便。

    25.4.1 硬件设计

    本实验主要使用FatFs软件功能,不需要其他硬件模块,使用与FatFs移植实验相同硬件配置即可。

    25.4.2 软件设计

    上个实验我们已经移植好了FatFs,这个例程主要是应用,所以简单起见,直接拷贝上个实验的工程文件,保持FatFs底层驱动程序,我们只改main.c文件内容,实现应用程序。

    FatFs多项功能测试

    代码清单 25-10 FatFs多项功能测试

    1 static FRESULT miscellaneous(void)

     2 {

     3     DIR dir;

     4     FATFS *pfs;

     5     DWORD fre_clust, fre_sect, tot_sect;

     6

     7     printf("\n*************** 设备信息获取 ***************\r\n");

     8     /* 获取设备信息和空簇大小 */

     9     res_flash = f_getfree("0:", &fre_clust, &pfs);

    10

    11     /* 计算得到总的扇区个数和空扇区个数 */

    12     tot_sect = (pfs->n_fatent - 2) * pfs->csize;

    13     fre_sect = fre_clust * pfs->csize;

    14

    15     /* 打印信息(4096 字节/扇区) */

    16     printf("》设备总空间:%10lu KB。\n》可用空间:  %10lu KB。\n",

    17                                              tot_sect *4, fre_sect *4);

    18

    19     printf("\n******** 文件定位和格式化写入功能测试 ********\r\n");

    20     res_flash = f_open(&fnew, "1:FatFs读写测试文件.txt",

    21                                     FA_OPEN_EXISTING|FA_WRITE|FA_READ );

    22     if ( res_flash == FR_OK ) {

    23         /*  文件定位 */

    24         res_flash = f_lseek(&fnew,f_size(&fnew)-1);

    25         if (res_flash == FR_OK) {

    26          /* 格式化写入,参数格式类似printf函数 */

    27          f_printf(&fnew,"\n在原来文件新添加一行内容\n");

    28          f_printf(&fnew,"》设备总空间:%10lu KB。\n》可用空间:  %10lu KB。\n",

    29                                                tot_sect *4, fre_sect *4);

    30             /*  文件定位到文件起始位置 */

    31             res_flash = f_lseek(&fnew,0);

    32             /* 读取文件所有内容到缓存区 */

    33             res_flash = f_read(&fnew,readbuffer,f_size(&fnew),&fnum);

    34             if (res_flash == FR_OK) {

    35                 printf("》文件内容:\n%s\n",readbuffer);

    36             }

    37         }

    38         f_close(&fnew);

    39

    40         printf("\n********** 目录创建和重命名功能测试 **********\r\n");

    41         /* 尝试打开目录 */

    42         res_flash=f_opendir(&dir,"0:TestDir");

    43         if (res_flash!=FR_OK) {

    44             /* 打开目录失败,就创建目录 */

    45             res_flash=f_mkdir("0:TestDir");

    46         } else {

    47             /* 如果目录已经存在,关闭它 */

    48             res_flash=f_closedir(&dir);

    49             /* 删除文件 */

    50             f_unlink("0:TestDir/testdir.txt");

    51         }

    52         if (res_flash==FR_OK) {

    53             /* 重命名并移动文件 */

    54             res_flash=f_rename("0:FatFs读写测试文件.txt",

    55                                               "0:TestDir/testdir.txt");

    56         }

    57     } else {

    58         printf("!! 打开文件失败:%d\n",res_flash);

    59         printf("!! 或许需要再次运行“FatFs移植与读写测试”工程\n");

    60     }

    61     return res_flash;

    62 }

    首先是设备存储信息获取,目的是获取设备总容量和剩余可用空间。f_getfree函数是设备空闲簇信息获取函数,有三个形参,第一个参数为逻辑设备编号;第二个参数为返回空闲簇数量;第三个参数为返回指向文件系统对象的指针。通过计算可得到设备总的扇区个数以及空闲扇区个数,对于QSPI Flash芯片我们设置每个扇区为4096字节大小。这样很容易就算出设备存储信息。

    接下来是文件读写指针定位和格式化输入功能测试。文件定位在一些场合非常有用,比如我们需要记录多项数据,但每项数据长度不确定,但有个最长长度,使用我们就可以使用文件定位lseek函数功能把数据存放在规定好的地址空间上。当我们需要读取文件内容时就使用文件定位函数定位到对应地址读取。

    使用文件读写操作之前都必须使用f_open函数打开文件,开始文件是读写指针是在文件起始位置的,马上写入数据的话会覆盖原来文件内容的。这里,我们使用f_lseek函数定位到文件末尾位置,再写入内容。f_lseek函数有两个形参,第一个参数为文件对象指针,第二个参数为需要定位的字节数,这个字节数是相对文件起始位置的,比如设置为0,则将文件读写指针定位到文件起始位置了。

    f_printf函数是格式化写入函数,需要把ffconf.h文件中的_USE_STRFUNC配置为1才支持。f_printf函数用法类似C库函数printf函数,只是它将数据直接写入到文件中。

    最后是目录创建和文件移动和重命名功能。使用f_opendir函数可以打开路径(这里不区分目录和路径概念,下同),如果路径不存在则返回错误,使用f_closedir函数关闭已经打开的路径。新版的FatFs支持相对路径功能,使路径操作更加灵活。f_opendir函数有两个形参,第一个参数为指向路径对象的指针,第二个参数为路径。f_closedir函数只需要指向路径对象的指针一个形参。

    f_mkdir函数用于创建路径,如果指定的路径不存在就创建它,创建的路径存在形式就是文件夹。f_mkdir函数只要一个形参,就是指定路径。

    f_rename函数是带有移动功能的重命名函数,它有两个形参,第一个参数为源文件名称,第二个参数为目标名称。目标名称可附带路径,如果路径与源文件路径不同见移动文件到目标路径下。

    文件信息获取

    代码清单 25-11  文件信息获取

    1 static FRESULT file_check(void)

     2 {

     3     FILINFO fno;

     4

     5     /* 获取文件信息 */

     6     res_flash=f_stat("0:TestDir/testdir.txt",&fno);

     7     if (res_flash==FR_OK) {

     8         printf("“testdir.txt”文件信息:\n");

     9         printf("》文件大小: %ld(字节)\n", fno.fsize);

    10         printf("》时间戳: %u/%02u/%02u, %02u:%02u\n",

    11             (fno.fdate >> 9) + 1980, fno.fdate >> 5 & 15, fno.fdate & 31,

    12              fno.ftime >> 11, fno.ftime >> 5 & 63);

    13         printf("》属性: %c%c%c%c%c\n\n",

    14                (fno.fattrib & AM_DIR) ? 'D' : '-',      // 是一个目录

    15                (fno.fattrib & AM_RDO) ? 'R' : '-',      // 只读文件

    16                (fno.fattrib & AM_HID) ? 'H' : '-',      // 隐藏文件

    17                (fno.fattrib & AM_SYS) ? 'S' : '-',      // 系统文件

    18                (fno.fattrib & AM_ARC) ? 'A' : '-');     // 档案文件

    19     }

    20     return res_flash;

    21 }

    f_stat函数用于获取文件的属性,有两个形参,第一个参数为文件路径,第二个参数为返回指向文件信息结构体变量的指针。文件信息结构体变量包含文件的大小、最后修改时间和日期、文件属性、短文件名以及长文件名等信息。

    路径扫描

    代码清单 25-12  路径扫描

    1 static FRESULT scan_files (char* path)

     2 {

     3     FRESULT res;    //部分在递归过程被修改的变量,不用全局变量

     4     FILINFO fno;

     5     DIR dir;

     6     int i;

     7     char *fn;        // 文件名

     8

     9 #if _USE_LFN

    10     /* 长文件名支持 */

    11     /* 简体中文需要2个字节保存一个“字”*/

    12     static char lfn[_MAX_LFN*2 + 1];

    13     fno.lfname = lfn;

    14     fno.lfsize = sizeof(lfn);

    15 #endif

    16     //打开目录

    17     res = f_opendir(&dir, path);

    18     if (res == FR_OK) {

    19         i = strlen(path);

    20         for (;;) {

    21             //读取目录下的内容,再读会自动读下一个文件

    22             res = f_readdir(&dir, &fno);

    23             //为空时表示所有项目读取完毕,跳出

    24             if (res != FR_OK || fno.fname[0] == 0) break;

    25 #if _USE_LFN

    26             fn = *fno.lfname ? fno.lfname : fno.fname;

    27 #else

    28             fn = fno.fname;

    29 #endif

    30             //点表示当前目录,跳过

    31             if (*fn == '.') continue;

    32             //目录,递归读取

    33             if (fno.fattrib & AM_DIR) {

    34                 //合成完整目录名

    35                 sprintf(&path[i], "/%s", fn);

    36                 //递归遍历

    37                 res = scan_files(path);

    38                 path[i] = 0;

    39                 //打开失败,跳出循环

    40                 if (res != FR_OK)

    41                     break;

    42             } else {

    43                 printf("%s/%s\r\n", path, fn);              //输出文件名

    44                 /* 可以在这里提取特定格式的文件路径 */

    45             }//else

    46         } //for

    47     }

    48     return res;

    49 }

    scan_files函数用来扫描指定路径下的文件。比如我们设计一个mp3播放器,我们需要提取mp3格式文件,诸如*.txt、*.c文件我们统统不可要的,这时我们就必须扫描路径下所有文件并把*.mp3或*.MP3格式文件提取出来。这里我们提取特定格式文件,而是把所有文件名称都通过串口打印出来。

    我们在ffconf.h文件中定义了长文件名称支持(_USE_LFN=2),一般有用到简体中文文件名称的都要长文件名支持。短文件名称是8.3格式,即名称是8个字节,后缀名是3个字节,对于使用英文名称还可以,使用中文名称就很容易长度不够了。使能了长文件名支持后,使用之前需要指定文件名的存储区还有存储区的大小。

    接下来就是使用f_opendir函数打开指定的路径。如果路径存在就使用f_readdir函数读取路径下内容,f_readdir函数可以读取路径下的文件或者文件夹,并保存信息到文件信息对象变量内。f_readdir函数有两个形参,第一个参数为指向路径对象变量的指针,第二个参数为指向文件信息对象的指针。f_readdir函数另外一个特性是自动读取下一个文件对象,即循序运行该函数可以读取该路径下的所有文件。所以,在程序中,我们使用for循环让f_readdir函数读取所有文件,并在读取所有文件之后退出循环。

    在f_readdir函数成功读取到一个对象时,我们还不清楚它是一个文件还是一个文件夹,此时我们就可以使用文件信息对象变量的文件属性来判断了,如果判断得出是个文件那我们就直接通过串口打印出来就好了。如果是个文件夹,我们就要进入该文件夹扫描,这时就重新调用扫描函数scan_files就可以了,形成一个递归调用结构,只是我们这次用的参数与最开始时候是不同的,现在是使用子文件夹名称。

    主函数

    代码清单 25-13 主函数

    1 int main(void)

     2 {

     3     /* 初始化调试串口,一般为串口1 */

     4     Debug_USART_Config();

     5     printf("******** 这是一个QSPI FLASH 文件系统实验 *******\r\n");

     6 FATFS_LinkDriver(&QSPI_Driver, QSPIPath);

     7     //在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化

     8     res_flash = f_mount(&fs,"0:",1);

     9     if (res_flash!=FR_OK) {

    10         printf("!!外部Flash挂载文件系统失败。(%d)\r\n",res_flash);

    11         printf("!!可能原因:QSPI Flash初始化不成功。\r\n");

    12         while (1);

    13     } else {

    14         printf("》文件系统挂载成功,可以进行测试\r\n");

    15     }

    16

    17     /* FatFs多项功能测试 */

    18     res_flash = miscellaneous();

    19

    20

    21     printf("\n*************** 文件信息获取测试 **************\r\n");

    22     res_flash = file_check();

    23

    24

    25     printf("***************** 文件扫描测试 ****************\r\n");

    26     strcpy(fpath,"0:");

    27     scan_files(fpath);

    28

    29

    30     /* 不再使用文件系统,取消挂载文件系统 */

    31     f_mount(NULL,"0:",1);

    32

    33     /* 操作完成,停机 */

    34     while (1) {

    35     }

    36 }

    串口在程序调试中经常使用,可以把变量值直观打印到串口调试助手,这个信息非常重要,同样在使用之前需要调用Debug_USART_Config函数完成调试串口初始化。

    使用FatFs进行文件操作之前都使用f_mount函数挂载物理设备,这里我们使用SPI Flash芯片上的FAT文件系统。

    接下来我们直接调用miscellaneous函数进行FatFs设备信息获取、文件定位和格式化写入功能以及目录创建和重命名功能测试。调用file_check函数进行文件信息获取测试。

    scan_files函数用来扫描路径下的所有文件,fpath是我们定义的一个包含100个元素的字符型数组,并将其赋值为SPI Flash芯片物理编号对于的根目录。这样允许scan_files函数见打印SPI Flash芯片内FatFs所有文件到串口调试助手。注意,这里的定义fpaht数组是必不可少的,因为scan_files函数本身是个递归函数,要求实际参数有较大空间的缓存区。

    25.4.3 下载验证

    保证开发板相关硬件连接正确,用USB线连接开发板“USB TO UART”接口跟电脑,在电脑端打开串口调试助手,把编译好的程序下载到开发板。程序开始运行,在串口调试助手可看到每个阶段测试相关信息情况。

    展开全文
  • NAND flash 文件系统制作

    千次阅读 2011-09-10 20:05:55
    提取到他的根文件系统映像: ------------------------------------- root@beagleboard:~# cd nand root@beagleboard:~# tar xfz <where_ever_your_root_fs_image_is_at_sd_card>/rootfs.tar.gz . ......
      
    

    BeagleBoardNAND

    Software

    以下的部分软件可以存储和从NAND启动/运行:

    • X-Loader
    • U-Boot (+ environment/configuration data)
    • Linux kernel
    • Linux file system

    这些软件在beagleboard上的存储分配如下:

    -------------------------------------
    0x00000000-0x00080000 : "X-Loader"
    0x00080000-0x00260000 : "U-Boot"
    0x00260000-0x00280000 : "U-Boot Env"

    0x00280000-0x00680000 : "Kernel"

    0x00680000-0x10000000 : "File System"

    -------------------------------------

    为了能够写东西到nandflash,首先你需要从其他源,如MMC/SD卡启动。此外,你需要把那些从MMC/SD卡启动的文件(MLO & U-Boot),写进MMC/SD卡得第一个分区。然后你可以在那里读取到这些文件和写入到NAND。

    X-Loader

    编译x-loader。放到MMC/SD卡的FAT分区,然后开始从存储卡中的引导和使用下面的命令写x-loader到NAND:

    -------------------------------------

    mmc init

    fatload mmc 0:1 80000000 x-load_revc_v3.bin.ift

    nand unlock

    nandecc hw

    nand erase 0 80000

    nand write 80000000 0 80000

    ------------------------------------------

    注:命令nandecc hw 在这里是必须的。x-loader是OMAP引导ROM的第一段程序。在读NAND的时候,使用命令nandecc hw 。所以,在写NAND时,同样使用使用命令nandecc hw 。如果你不使用nandecc hw 将不能从NAND 引导ROM。

    U-Boot

    编译U-Boot。放到MMC/SD卡的FAT分区,然后开始从存储卡中的引导和使用下面的命令写U-Boot到NAND:

    -------------------------------------

    mmc init

    fatload mmc 0:1 80000000 u-boot.bin

    nand unlock

    nandecc sw

    nand erase 80000 160000

    nand write 80000000 80000 160000

    -------------------------------------

    注意:你可以使用引导MMC/SD卡启动的u-boot.bin来引导NAND启动。他们之间没有区别。

    注意:这儿,你不需要nandecc hw选项。X-loader用于加载和引导u-boot,能够识别u-boot写入的SW ECC。

    Kernel

    相比于只能通过u-boot写入NAND的x-loader和u-boot,这里有两种方法将kernel和file system写入NAND:通过u-boot(与上面的x-loader和u-boot相似) 或者从运行中的内核(比如,从MMC卡引导)。

    注意:x-loader和u-boot不能从运行中得内核中写入,因为从内核的角度, x-loader和u-boot的NAND分区是标记为只读的。

    Writing kernel with U-Boot

    -------------------------------------

    mmc init

    fatload mmc 0:1 80000000 uImage

    nandecc sw

    nand erase 280000 400000

    nand write 80000000 280000 400000

    -------------------------------------

    当做了上述工作之后,使用U-Boot命令从NAND引导kernel (uImage):

    nand read 80000000 280000 400000 ; bootm 80000000

    这里,你可以保存U-Boot命令,这样你的板子就可以自动的从NAND引导uImage。

    Writing kernel with kernel

    当你有一个内核启动了,比如:从MMC卡。你可以使用该内核去写自己的kernel (uImage)去NAND。然后从MMC卡启;动切换到NAND卡启动。对于这一点,观察内核的引导信息。这些应该是这样的:
    -------------------------------------
    omap2-nand driver initializing
    NAND device: Manufacturer ID: 0x2c, Chip ID: 0xba (Micron NAND 256MiB 1,8V 16-bit)
    cmdlinepart partition parsing not available
    Creating 5 MTD partitions on "omap2-nand":
    0x00000000-0x00080000 : "X-Loader"
    0x00080000-0x00260000 : "U-Boot"
    0x00260000-0x00280000 : "U-Boot Env"
    0x00280000-0x00680000 : "Kernel"
    0x00680000-0x10000000 : "File System"
    -------------------------------------

    在内核的提示命令 cat /proc/mtd 下会给出类似的输出信息:

    -------------------------------------
    root@beagleboard:~# cat /proc/mtd
    dev:    size   erasesize  name
    mtd0: 00080000 00020000 "X-Loader"
    mtd1: 001e0000 00020000 "U-Boot"
    mtd2: 00020000 00020000 "U-Boot Env"
    mtd3: 00400000 00020000 "Kernel"
    mtd4: 0f980000 00020000 "File System"
    -------------------------------------

    虽然前三个分区(x-loader,u-boot和u-boot Env),从内核的角度查看是只读的。但是kernel和file system 分区对于内核本身来说是可写的。要做到这一点,你需要有MTD用户模块在你的内核根文件系统里。

    在这个例子中,我们挂在MMC卡得FAT分区去读kernel(uImage)。如果你的内核有网络连接,你可以使用此。或者把uImage放在你的根文件系统。这样做的目的是能够从正在运行的内核中将uImage写入到NAND。

    -------------------------------------
    root@beagleboard:~# mkdir -p /mnt/fat
    root@beagleboard:~# mount /dev/mmcblk0p1 /mnt/fat/
    root@beagleboard:~# ls -la /mnt/fat
    -rwxr-xr-x    1 root     root        16740 Jul  7 17:28 mlo
    -rwxr-xr-x    1 root     root       717116 Jul 24  2008 u-boot.bin
    -rwxr-xr-x    1 root     root      2106940 Jul 26  2008 uImage
    root@beagleboard:~# cp /mnt/fat/uImage .
    root@beagleboard:~# ls -la
    -rwxr-xr-x    1 root     root      2106940 Jul 22 00:30 uImage
    root@beagleboard:~# flash_eraseall /dev/mtd3
    Erasing 128 Kibyte @ 3e0000 -- 96 % complete.
    root@beagleboard:~# nandwrite -p /dev/mtd3 uImage
    Writing data to block 0 at offset 0x0
    <...>
    Writing data to block 20 at offset 0x280000
    root@beagleboard:~#
    -------------------------------------

    File system

    与内核一样,相比于只能通过u-boot写入NAND的x-loader和u-boot,这里有两种方法将file system写入NAND:通过u-boot(与上面的x-loader和u-boot相似) 或者 从运行中的内核(比如,从MMC卡引导)。很多用户反映,他们在用u-boot写根文件系统时遇到了问题。主要的问题是,u-boot必须格式化内核去写file system。如果有轻微的不兼容,内核将不能读取到被u-boot写入的文件系统。

    所以,我们在这里记录了如何去用u-boot写入文件系统。推荐的方法是通过内核本身去写根文件系统。有了这个,可以确保用内核写入一个文件系统,稍后能够读取到。

    Writing file system with U-Boot

    该方法不推荐,原因见上。

    -------------------------------------
    OMAP3 beagleboard.org # mmc init
    OMAP3 beagleboard.org # fatload mmc 0:1 80000000 rootfs.jffs2
    reading rootfs.jffs2
    12976128 bytes read
    OMAP3 beagleboard.org # nand unlock
    device 0 whole chip
    nand_unlock: start: 00000000, length: 268435456!
    NAND flash successfully unlocked
    OMAP3 beagleboard.org # nandecc sw
    OMAP3 beagleboard.org # nand erase 680000 F980000
    NAND erase: device 0 offset 0x680000, size 0xf980000
    Erasing at 0xffe0000 -- 100% complete.
    OK
    OMAP3 beagleboard.org # nand write.jffs2 80000000 680000 ${file_size}
    NAND write: device 0 offset 0x680000, size 0xc60000
    Writing data at 0x12df800 -- 100% complete.
     12976128 bytes written: OK
    -------------------------------------

    Writing file system with kernel

    推荐该方法。

    首先,我们用SD卡上的根文件系统启动内核。在正在运行的内核中,用SD卡上的文件系统映像把根文件系统写入到Beagle的NAND上去。在这些做完之后,我们切换内核的启动参数去从NAND上执行根文件系统来替代SD卡得根文件系统。

    然后,为了能够从内核的用户空间去操纵/擦除/写NAND,我们需要MTD Utils。如果你没有这个工具,你可以通过该方法获取:opkg install mtd-utils 

    对于在Beagle’s NAND 中的文件系统,我们使用JFFS2。JFFS2是标准的git内核的一部分,只需要配置内核使能JFFS2。使用make menuconfig检查是否已启用。

    -------------------------------------

    CONFIG_JFFS2_FS=y

    CONFIG_JFFS2_FS_DEBUG=0

    CONFIG_JFFS2_FS_WRITEBUFFER=y

    CONFIG_JFFS2_ZLIB=y

    CONFIG_JFFS2_RTIME=y

    -------------------------------------

    在内核支持JFFS2和MTD Utils后,我们先擦除文件系统分区并在该分区中创建JFFS2。

    -------------------------------------

    root@beagleboard:~# cat /proc/mtd

    dev:    size   erasesize  name

    mtd0: 00080000 00020000 "X-Loader"

    mtd1: 001e0000 00020000 "U-Boot"

    mtd2: 00020000 00020000 "U-Boot Env"

    mtd3: 00400000 00020000 "Kernel"

    mtd4: 0f980000 00020000 "File System"

    root@beagleboard:~# flash_eraseall -j /dev/mtd4

    Erasing 128 Kibyte @ f960000 -- 99 % complete. Cleanmarker written at f960000.

    -------------------------------------

    然后,我们可以挂载文件系统分区:

    -------------------------------------

    root@beagleboard:~# cd /mnt

    root@beagleboard:~# mkdir nand

    root@beagleboard:~# mount -t jffs2 /dev/mtdblock4 /mnt/nand

    -------------------------------------

    并提取到他的根文件系统映像:

    -------------------------------------

    root@beagleboard:~# cd nand

    root@beagleboard:~# tar xfz <where_ever_your_root_fs_image_is_at_sd_card>/rootfs.tar.gz .

    ... wait ...

    root@beagleboard:~# cd ..

    root@beagleboard:~# sync

    root@beagleboard:~# umount nand

    -------------------------------------

    现在,你应该重启你的主板并在u-boot中编辑启动参数去配置在NAND中的根文件系统。

    -------------------------------------

    root=/dev/mtdblock4 rootfstype=jffs2

    -------------------------------------

     

    展开全文
  • 在STM32ZET6移植文件系统FatFs,以文件的形式存储数据到flash中。并给出了恢复w25q128的出厂文件的方法。



      先对内存存储有一个理解,比如在FALSH中存储数据时,已知在STM32F1 开发板上都有自带有一个外部 FLASH(W25Q128、128Mbit=16MByte,即16M内存),FLASH存储的数据掉电不会丢失里面的数据,MCU和W25Q128是采用SPI进行通信的。可以查看ROM、RAM、DRAM、SRAM和FLASH的区别单片机中为什么有了Flash还有EEPROM?

      W25Q128将16M的空间分成256个块(block),其中每一个块为64KB大小;同时,每一个块再细分为16个扇区(sector),其中每一个扇区为4KB大小,然后一个扇区还可以再分为16页,每一页是256个字节的大小。W25Q128规定每一次最大允许一次写入一页的数据,每一次写之前都需要擦除一个扇区的大小,如果需要写入更多的数据,则需要写完一页再写一页。

      当要向W25Q128写入数据时,先传输目标地址(即要写入哪个sector),然后检查这一块扇区是否为空,如果不为空(即使只有一个位的数据也算不为空),需要先将该扇区进行擦除,擦除干净再写入(擦除其实是将整个扇区都置为1);读取数据时也需要传输需要读取哪个扇区的地址;当一个扇区要多次写入数据时(比如我想接着上一次写完的后面写入),可以定义一个全局变量数组,先将原本该扇区的数据都保存到该数组,再将新数据与这个数组进行拼接,然后一次性写入到FLASH。这里需要至少 4K 的缓存区,要求芯片必须有 4K 以上 SRAM 才能很好的操作。

      比如当需要记录字符“STM32 SPI FLASH”时,这些文字会转化成ASCII码存储在数组中,数组数据通过SPI传输到W25Q128的指定地址上,在需要的时候再该地址把数据读取出来,再对读出来的数据以ASCII码的格式进行解读。

      个人觉得正点原子的代码比较规范和强大,而野火的代码和教程更能让人理解,以下是根据他们的代码理解,增加了一些注释以便理解。主要是关于w25q128的读和写函数,工程下载链接为:移植前工程1(spi flash简单读写).rar【提取码: 9r9h】

    w25qxx.c

    //读取SPI FLASH  
    //在指定地址开始读取指定长度的数据
    //pBuffer:数据存储区
    //ReadAddr:开始读取的地址(24bit)
    //NumByteToRead:要读取的字节数(最大65535)
    void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   
    { 
     	u16 i;   										    
    	W25QXX_CS=0;                            	//使能器件   
      	SPI2_ReadWriteByte(W25X_ReadData);         	//发送读取命令   
      	SPI2_ReadWriteByte((u8)((ReadAddr)>>16));  	//发送24bit地址    
      	SPI2_ReadWriteByte((u8)((ReadAddr)>>8));   
      	SPI2_ReadWriteByte((u8)ReadAddr);   
      	for(i=0;i<NumByteToRead;i++)
    	{ 
          	pBuffer[i]=SPI2_ReadWriteByte(0XFF);   	//循环读数  
      	}
    	W25QXX_CS=1;  				    	      
    }
    
    //页面编程指令允许从一个字节到256字节(一页)的数据进行编程
    //一个扇区分为16页(1扇区=4096字节=16*256)
    //在指定地址开始写入最大256字节的数据
    //pBuffer:数据存储区
    //WriteAddr:开始写入的地址(24bit)
    //NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!	 
    void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
    {
     	u16 i;  
      	W25QXX_Write_Enable();                  	//SET WEL 
    	W25QXX_CS=0;                            	//使能器件   
      	SPI2_ReadWriteByte(W25X_PageProgram);      	//发送写页命令   
      	SPI2_ReadWriteByte((u8)((WriteAddr)>>16)); 	//发送24bit地址    
      	SPI2_ReadWriteByte((u8)((WriteAddr)>>8));   
      	SPI2_ReadWriteByte((u8)WriteAddr);   
      	for(i=0;i<NumByteToWrite;i++) SPI2_ReadWriteByte(pBuffer[i]);//循环写数  
    	W25QXX_CS=1;                            	//取消片选 
    	W25QXX_Wait_Busy();					   		//等待写入结束
    } 
    
    //写SPI FLASH  
    //在指定地址开始写入指定长度的数据
    //该函数带擦除操作!
    //pBuffer:数据存储区
    //WriteAddr:开始写入的地址(24bit)						
    //NumByteToWrite:要写入的字节数(最大65535)   
    u8 W25QXX_BUFFER[4096];	//全局变量,在SRAM,用于缓冲暂存	 
    void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
    { 
    	u32 secpos;
    	u16 secoff;
    	u16 secremain;	   
     	u16 i;    
    	u8 * W25QXX_BUF;	  
      	W25QXX_BUF=W25QXX_BUFFER;	     
     	secpos=WriteAddr/4096;//扇区地址  
    	secoff=WriteAddr%4096;//在扇区内的偏移
    	secremain=4096-secoff;//扇区剩余空间大小(字节)   
     	//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
     	if(NumByteToWrite<=secremain) secremain = NumByteToWrite;//不大于4096个字节
    	while(1) 
    	{	
    		W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容
    		for(i=0;i<secremain;i++)//校验数据
    		{
    			if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除  	  
    		}
    		if(i<secremain)//需要擦除
    		{
    			W25QXX_Erase_Sector(secpos);		//擦除这个扇区
    			for(i=0;i<secremain;i++)	   		//复制
    			{
    				W25QXX_BUF[i+secoff]=pBuffer[i];	  
    			}
    			W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区  
    
    		}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   
    		if(NumByteToWrite==secremain)break;//写入结束了
    		else//写入未结束
    		{
    			secpos++;//扇区地址增1
    			secoff=0;//偏移位置为0 	 
    
    		  	pBuffer+=secremain;  				//指针偏移
    			WriteAddr+=secremain;				//写地址偏移	   
    		  	NumByteToWrite-=secremain;			//字节数递减
    			if(NumByteToWrite>4096)secremain=4096;//下一个扇区还是写不完
    			else secremain=NumByteToWrite;		//下一个扇区可以写完了
    		}	 
    	}	 
    }
    

      正点原子的STM32战舰版,W25Q128和SPI2连在一起,同时在出厂时他们预留了一些文件在该FLASH里了,他们将 W25Q128 的前 12M 字节给 FATFS 管理(用做本地磁盘,是的,里面已经有了FATFS的格式,不过需要对该文件系统进行读写的话还要移植代码来实现),随后,紧跟字库结构体、UNIGBK.bin、和三个字库,这部分内容首地址是:(1024*12)*1024,大小约 3.09M,最后还剩下约 0.9M 给用户自己用。因此在下面的main测试函数里,我们可以将少量数据写入到倒数第100个字节,不会对原有文件造成影响。

    main.c

    #include "led.h"
    #include "delay.h"
    #include "key.h"
    #include "sys.h"
    #include "usart.h"	 
    #include "w25qxx.h"	 
     				 	
    //要写入到W25Q64的字符串数组
    const u8 TEXT_Buffer[]={"WarShipSTM32 SPI TEST"};
    #define SIZE sizeof(TEXT_Buffer)
    
    int main(void)
    {	 
    	u8 key;
    	u16 i=0;
    	u8 datatemp[SIZE];
    	u32 FLASH_SIZE=16*1024*1024; //总共16M的大小
    	 
    	delay_init();	    	 //延时函数初始化	  
      	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
    	uart_init(115200);	 	//串口初始化为115200
    	LED_Init();		  		//初始化与LED连接的硬件接口
    	KEY_Init();				//按键初始化		 	 	
    	W25QXX_Init();			//W25QXX初始化
    	
    	printf("KET0: 读出\tKEY1: 写入\r\n\n");
    	while(W25QXX_ReadID()!=W25Q128)	LED0 = 0;
    	  
    	while(1)
    	{
    		key=KEY_Scan(0);
    		if(key==KEY1_PRES)	//KEY1按下,写入W25QXX
    		{
    			W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE);	//从倒数第100个地址处开始,写入SIZE长度的数据
    			printf("写入数据:%s,长度: %d\r\n\r\n",TEXT_Buffer,SIZE);
    		}
    		if(key==KEY0_PRES)	//KEY0按下,读取字符串并显示
    		{
    			W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE);					//从倒数第100个地址处开始,读出SIZE个字节
    			printf("读出的数据为:%s,长度: %d\r\n\r\n",datatemp,SIZE);
    		}
    		i++;
    		delay_ms(10);
    		if(i==20)
    		{
    			LED0=!LED0;//提示系统正在运行	
    			i=0;
    		}		   
    	}
    }
    
    

     运行结果如下:

      从以上的概述来看,存储和读取数据怎么这么困难呢?今天我写入了某些数据在某个单元,明天起床我想再写入数据或者读出数据的时候,地址我忘了怎么办?并且想读取的这份数据又多长(几个字节)我也不知道,这样子就很麻烦。在以后实际的应用中,其实还会涉及到以怎样的格式进行读取FLASH,比如电脑中一些文件都有默认的打开方式,打开mp3文件就自动播放音乐,mp4就是视频,过程其实就是将FLASH读取出来的数据直接当作需要解码的数据,这样就会很快。如果仅仅是上面这样子存储数据在FLASH,那就太难了。

      有一个思路:可以将W25Q128中前面一部分扇区专门用来存储有关文件信息,比如文件存在哪里,有多长,文件的内容数据再存储到其他的扇区,这样一来,以后读取一个文件前,可以先读取前面的扇区取出有关文件的信息。

      文件系统就是专门用来管理这些文件的储存的,它统筹了每个文件存储的位置、大小长度、格式等记录信息,说白了就是对数据进行管理的方式。使用文件系统可有效地管理存储介质。首次使用文件系统时,先对存储介质进行格式化(类似于划定说哪一部分负责什么什么),即在存储介质建立了一些组织结构,这些结构包括操作系统引导区、目录和文件,也就是文件分配表和目录了。

      先看看FatFs移植到stm32后得样子,以前保存数据到FLASH,都是要一次一次得保存,裸着的数据,现在有了文件系统,就能够和在电脑上一样用代码对文件操作了

      存储一份数据时,都以文件的形式存储,此时可以将文件理解为“特定格式说明信息+原本的数据”,文件系统先在目录中创建一个文件索引,它指示了文件存放的物理地址,再把数据存储到该地址中。当需要读取数据时,可以从目录中找到该文件的索引,进而在相应的地址中读取出数据。这个读写过程并不是和文首提到的那样直接读写,而是要遵循文件系统的读写格式。如经过逻辑转换,一个完整的文件可能被分开成多段存储到不连续的物理地址,使用目录或链表的方式来获知下一段的位置。

      簇,可以理解为扇区,每一部分的扇区都有自己特定的任务使命,关于目录比较容易理解,只是简单的一个索引,它指明了一个文件的基本信息和它的存储位置起始地址,和存储的扇区空间大小,那结束地址呢?是不是起始地址加上大小就是结束地址呢?并不是,如前文提到的一个完整的文件可能被分开成多段存储到不连续的物理地址。这时候就用到了文件分配表,它非常重要。

      接上图,B文件大小为54个扇区,这是假如将它删除,则在A文件和C文件之间就会有空出54个扇区,下一次当要保存新文件比如D文件大小为61个扇区,这时候文件系统就会将D文件拆分成两部分,一部分存在A和C之间,另一部分则存在C的后面。这就是文件分配表的魅力所在。目录记录了某个文件存储在哪一个扇区,而文件分配表则记录了某个扇区存储了哪些文件。

      文件分配表记录了第一簇记录的是目录,第二簇记录的是数据,并且读完第二簇之后指引系统接着读取第三簇的数据,第三簇记录的是数据并且指引到第四簇,以此类推,直到读完第11簇,指引指向FF,表示为空,此时文件读取结束。

      B文件删除之前的文件分配表长这个样子:

      删除B文件之后并新建了D文件后的文件分配表长这个样子:读D文件时,当第65簇读完了,则接着读第87个簇。

      具体如何通过文件分配表和目录来读取出一个文件里的数据,然后目录里的哪些字节代表什么之类的这些细节,这就是用代码来实现并管理,本文介绍的是嵌入式中常用的FatFs文件系统(理解白了,它其实就是一些代码 .c .h 文件)。

      FatFs文件系统源码下载地址

      利用前面写好的SPI Flash芯片驱动(即对W25Q128进行读写),把FatFs文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对SPI Flash芯片以“文件”格式进行读写操作了。

    • 最顶层是应用层,使用者无需理会 FATFS 的内部结构和复杂的 FAT 协议,只需要调用FATFS 模块提供给用户的一系列应用接口函数,如 f_open,f_read,f_write 和 f_close 等,就可以像在 PC 上读/写文件那样简单。
    • 中间层 FATFS 模块,实现了 FAT 文件读/写协议。FATFS 模块提供的是 ff.c 和 ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去即可。
    • 对于底层接口则需要我们自己来编写,因为对存储媒介读/写接口(disk I/O)有比如IIC读取,SPI读取,具体读什么芯片和怎么读这些操作则根据自己的情况来写。比如我们的是SPI FLASH,关于W25Q128的读写我们在上面已经能够实现,则接下来要做的就是把这些读写、初始化等W25Q128函数“绑定”到FatFs中就好了。

      比如调用f_write函数时,FatFs就会调用到disk_write函数,需要用户自己实现要写入的数据、写入哪个扇区、使用多少扇区、扇区有多大等。

      以下是具体的移植过程,需要将FATFS文件加载到工程直接编译时会报错,没关系,跟着下面修改diskio.c和ffconf.f就好。

      先将文件导入到工程,并包含头文件路径

      
     将diskio.c更改成下面的,正点原子的例程是强制将其扇区定义为 512 字节,这样带来的好处就是设计使用相对简单,但同时写入相同的数据里量却增加了擦除次数,频繁写的话,很容易将 SPI FLASH 写坏。因此下面是以原本一个扇区为4K这样子来操作的,其占用的内存为前12M。

    #include "diskio.h"		/* FatFs lower layer API */
    #include "w25qxx.h"		/*包含对flash初始化、读写相关函数*/
    #include "usart.h"	 
    
    
    //以两个设备为例,SD卡的先不写,只是让系统知道有两个选择,在应用层指明用哪个就行
    #define SD_CARD   0		//存储介质为SD卡
    #define W25QXX    1		//存储介质为FLASH
    
    #define FLASH_SECTOR_SIZE 	4096  //一个扇区的大小			  
    //前12M字节给fatfs用,12M字节后,用于存放字库,字库占用3.09M.	剩余部分,给客户自己用	 			    
    #define	FLASH_SECTOR_COUNT 3072;	//总扇区数:256*16个,FATFS占12M,即(12/16)*256*16=3072
    #define FLASH_BLOCK_SIZE   	16     	//每个BLOCK有16个扇区
    
    
    /*-----------------------------------------------------------------------*/
    /* Get Drive Status  获取设备的状态,比如  初始化是否成功                   */
    /*-----------------------------------------------------------------------*/
    DSTATUS disk_status (
    	BYTE pdrv		/* Physical drive nmuber to identify the drive */
    )
    {
    	DSTATUS stat;
    
    	switch (pdrv) {
    	case SD_CARD :
    		//result = ATA_disk_status();//暂时还不用SD卡
    		return stat;
    
    	case W25QXX:
    		{
    			//通过读取FLASH的ID判断是否正常
    			if(W25QXX_ReadID()!= W25Q128)
    			{
    				//状态不正常
    				stat = STA_NOINIT;//#define STA_NOINIT	0x01
    			}
    			else
    			{
    				//状态正常
    				stat = 0;		
    			}
    			return stat;
    		}
    	}
    	return STA_NOINIT;
    }
    
    /*-----------------------------------------------------------------------*/
    /* Inidialize a Drive   使用f_mount挂载文件系统时时,就调用到这个函数,把W25Q128顺便初始化了          */
    /*-----------------------------------------------------------------------*/
    DSTATUS disk_initialize (
    	BYTE pdrv				/* Physical drive nmuber to identify the drive */
    )
    {
    	DSTATUS stat;
    
    	switch (pdrv) {
    	case SD_CARD :
    		//result = ATA_disk_initialize();
    		return stat;
    
    	case W25QXX :
    		W25QXX_Init();
    	  W25QXX_WAKEUP();//防止FLASH设备处于低功耗状态
    		return  disk_status(W25QXX);//通过读取设备ID来检查设备是否初始化正常
    	}
    	return STA_NOINIT;
    }
    
    
    //读扇区
    //pdrv:磁盘编号0~9
    //*buff:数据接收缓冲首地址
    //sector:扇区编号
    //count:需要读取的扇区数
    DRESULT disk_read (
    	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
    	BYTE *buff,		/* Data buffer to store read data */
    	DWORD sector,	/* Sector address in LBA */
    	UINT count		/* Number of sectors to read */
    )
    {
    	DRESULT res;
    
    	switch (pdrv) {
    	case SD_CARD :
    		//result = ATA_disk_read(buff, sector, count);
    		return res;
    
    	case W25QXX :
    		//FatFs使用扇区编号访问扇区
    		//扇区是有编号的,0号扇区对应的地址为0~4096,1号扇区对应地址4096~8192,。。。
    	  W25QXX_Read(buff, sector*FLASH_SECTOR_SIZE, count*FLASH_SECTOR_SIZE);
    	  res = RES_OK;//这里只是简单的,读函数可以自行设置返回值
    		return res;
    	}
    	return RES_PARERR;
    }
    
    //写扇区
    //pdrv:磁盘编号0~9
    //*buff:发送数据首地址
    //sector:扇区编号
    //count:需要写入的扇区数
    #if _USE_WRITE
    DRESULT disk_write (
    	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
    	const BYTE *buff,	/* Data to be written */
    	DWORD sector,		/* Sector address in LBA */
    	UINT count			/* Number of sectors to write */
    )
    {
    	DRESULT res;
    
    	switch (pdrv) {
    		case SD_CARD :
    			return res;
    
    		case W25QXX :
    			//W25QXX_Erase_Sector(sector*FLASH_SECTOR_SIZE);//一定要先擦除再写入
    			//不能使用 writePageProgram,要能够支持多页写入的
    		  W25QXX_Write((u8*)buff,sector*FLASH_SECTOR_SIZE, count*FLASH_SECTOR_SIZE);
    	    res = RES_OK;
    			return res;
    	}
    	return RES_PARERR;
    }
    #endif
    
    /*-----------------------------------------------------------------------*/
    /*用来格式化的函数,通过cmd与底层flash交互,获取flash信息等                            */
    /*-----------------------------------------------------------------------*/
    #if _USE_IOCTL
    DRESULT disk_ioctl (
    	BYTE pdrv,		/* Physical drive nmuber (0..) */
    	BYTE cmd,		/* Control code */
    	void *buff		/* Buffer to send/receive control data */
    )
    {
    	DRESULT res;
    
    	switch (pdrv) {
    	case SD_CARD :
    		return res;
    
    	case W25QXX :
    	{
    		switch(cmd)
    		{
    			case CTRL_SYNC:
    				res = RES_OK; 
    		    break;
    			
    			//返回扇区个数
    			case GET_SECTOR_COUNT:
    				//typedef unsigned long	DWORD;
    				*(DWORD*)buff = FLASH_SECTOR_COUNT;//12M空间用于fatfs
    			  res = RES_OK;
    				break;
    			
    			//返回每个扇区大小
    			case GET_SECTOR_SIZE:
    				*(WORD*)buff = FLASH_SECTOR_SIZE;
    			  res = RES_OK;
    				break;
    			
    			//返回擦除扇区的最小个数(单位为扇区)
    			case GET_BLOCK_SIZE:
    			 *(WORD*)buff = 1;
    			  res = RES_OK;
    				break;
    			default:
    		    res = RES_PARERR;
    		    break;
    		}	
    		return res;
    	}
     }
     return RES_PARERR;
    }
    #endif
    
    //返回时间,这里没用到,直接返回一个值
    DWORD get_fattime (void)
    {
    
    	return 0;
    }
    
    

    ffconf.f:跟着下面的更改即可,同时也加入了cc936.c文件,主要提供 UNICODE 到 GBK 以及 GBK 到 UNICODE 的码表转换,里面就是两个大数组,并提供一个 ff_convert 的转换函数,供 UNICODE 和 GBK 码互换,这个在中文长文件名支持的时候必须用到。该部分编码数组(很大)会保存到内部的内存,所以烧录代码的时间明显增长(后面可以采用将编码数组放在w25q128中)。

    #define _FFCONF 64180	/* Revision ID */
    
    #define _FS_READONLY	0
    
    #define _FS_MINIMIZE	0
    
    #define	_USE_STRFUNC	1 //为了使用f_printf写函数
    
    #define _USE_FIND		0
    
    #define	_USE_MKFS		1 //使能格式化功能
    
    #define	_USE_FASTSEEK	1 //使能或禁用快速搜索功能,使能后,可以加快f_lseek、f_read和f_write函数执行。
    
    #define _USE_LABEL		0
    
    #define	_USE_FORWARD	0
    
    #define _CODE_PAGE	936 //中文编码
    
    #define	_USE_LFN	1
    #define	_MAX_LFN	255
    
    #define	_LFN_UNICODE	0
    
    #define _STRF_ENCODE	3
    
    #define _FS_RPATH	0
    
    #define _VOLUMES	2 //两个存储介质,一个是SD_CARD,一个是flash,默认为1的
    
    #define _STR_VOLUME_ID	0
    #define _VOLUME_STRS	"RAM","NAND","CF","SD1","SD2","USB1","USB2","USB3"
    
    #define	_MULTI_PARTITION	0
    
    #define	_MIN_SS		512
    #define	_MAX_SS		4096//因为FATFS.win[_MAX_SS] 和 case GET_SECTOR_SIZE: *(WORD*)buff = 4096;
    
    #define	_USE_TRIM	0
    
    #define _FS_NOFSINFO	0
    
    #define	_FS_TINY	0
    
    #define _FS_NORTC	0
    #define _NORTC_MON	1
    #define _NORTC_MDAY	1
    #define _NORTC_YEAR	2015
    
    #define	_FS_LOCK	0
    
    #define _FS_REENTRANT	0
    #define _FS_TIMEOUT		1000
    #define	_SYNC_t			HANDLE
    
    #define _WORD_ACCESS	0
    

    main.c:因为出厂即带有了FATFS系统,因此第一次挂载时按理论来说会挂载失败,因为其定义为512字节一个扇区,而代码是4K一个扇区,因此应该会重新格式化,并在第二次挂载成功,如果不成功,可以在diskio.c的写函数中增加擦除的函数。

    #include "led.h"
    #include "delay.h"
    #include "key.h"
    #include "sys.h"
    #include "usart.h"	 
    #include "w25qxx.h"
    #include "ff.h"
    #include <string.h>
     				 	
    FATFS fsObject;//一个很大的结构体对象,fatfs依赖它存储文件系统运行时的一些状态、数据等
    FIL fp; //文件句柄
    const char wData[]="哇咔咔,FatFs真好用!";
    char rData[4096]="";//读取flash接收缓存
    UINT bw;//写入成功的数据长度
    UINT br;//读取成功的数据长度
    
    FRESULT getDiskInfo(void)
    {
      FATFS *pfs;
      DWORD fre_clust, fre_sect, tot_sect;
    	FRESULT res_flash;                /* 文件操作结果 */
    	char readbuffer[512];	
      
      printf("\n*************** 设备信息获取 ***************\r\n");
      /* 获取设备信息和空簇大小 */
      res_flash = f_getfree("1:", &fre_clust, &pfs);
    
      /* 计算得到总的扇区个数和空扇区个数 */
      tot_sect = (pfs->n_fatent - 2) * pfs->csize;//n_fatent为簇的数目+2
      fre_sect = fre_clust * pfs->csize;
    
      /* 打印信息(4096 字节/扇区) */
      printf("》设备总空间:%10lu KB。\n》可用空间:  %10lu KB。\n", tot_sect *4, fre_sect *4);
      
      printf("\n******** 文件定位和格式化写入功能测试 ********\r\n");
      res_flash = f_open(&fp, "1:FatFs读写测试文件.txt",FA_OPEN_ALWAYS|FA_WRITE|FA_READ);
    	if ( res_flash == FR_OK )
    	{
        /*  文件定位到文件末尾 */
        res_flash = f_lseek(&fp,f_size(&fp));
        if (res_flash == FR_OK)
        {
          /* 格式化写入,参数格式类似printf函数 */
          f_printf(&fp,"在原来文件新添加一行内容\n");
          f_printf(&fp,"》正在测试,设备总空间:%10lu KB。\t可用空间:  %10lu KB。\n", tot_sect *4, fre_sect *4);
          /*  文件定位到文件起始位置 */
          res_flash = f_lseek(&fp,0);
          /* 读取文件所有内容到缓存区 */
          res_flash = f_read(&fp,readbuffer,f_size(&fp),&br);
          if(res_flash == FR_OK)
          {
            printf("》文件内容:%s",readbuffer);
          }
        }
        f_close(&fp);
    		//f_unlink("1:/FatFs读写测试文件.txt");		//删除某个文件
    	}		
      return res_flash;
    }
    
    FRESULT scan_files(char* path) 
    { 
      FRESULT res; 		//部分在递归过程被修改的变量,不用全局变量	
      FILINFO fno; 
      DIR dir; 
      int i;            
      char *fn;        // 文件名	
    	
    #if _USE_LFN 
      /* 长文件名支持 */
      /* 简体中文需要2个字节保存一个“字”*/
      static char lfn[_MAX_LFN*2 + 1]; 	
      fno.lfname = lfn; 
      fno.lfsize = sizeof(lfn); 
    #endif 
      //打开目录
      res = f_opendir(&dir, path); 
      if (res == FR_OK) 
    	{ 
        i = strlen(path); 
        for (;;) 
    		{ 
          //读取目录下的内容,再读会自动读下一个文件
          res = f_readdir(&dir, &fno); 								
          //为空时表示所有项目读取完毕,跳出
          if (res != FR_OK || fno.fname[0] == 0) break; 	
    #if _USE_LFN 
          fn = *fno.lfname ? fno.lfname : fno.fname; 
    #else 
          fn = fno.fname; 
    #endif 
          //点表示当前目录,跳过			
          if (*fn == '.') continue; 	
          //目录,递归读取      
          if (fno.fattrib & AM_DIR)         
    			{ 			
            //合成完整目录名        
            sprintf(&path[i], "/%s", fn); 		
            //递归遍历         
            res = scan_files(path);	
            path[i] = 0;         
            //打开失败,跳出循环        
            if (res != FR_OK) 
    					break; 
          } 
    			else 
    			{ 
    				printf("%s/%s\r\n", path, fn);								//输出文件名	
            /* 可以在这里提取特定格式的文件路径 */        
          }//else
        } //for
      } 
      return res; 
    }
    	
    int main(void)
    {	
    	FRESULT res;
    	u8 key;
    	uint16_t i=0;
    	 
    	delay_init();	    	 //延时函数初始化	  
      NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
    	uart_init(115200);	 	//串口初始化为115200
    	LED_Init();		  		//初始化与LED连接的硬件接口
    	KEY_Init();				//按键初始化		 	 	
    	
    	/*
    	FRESULT f_mount (
    	  FATFS*       fs,    
    	  const TCHAR* path,  //路径
    	  BYTE         opt    
    	);
    	//FRESULT是返回类型,一个枚举变量,表示文件系统挂载时的多种状态,0表示正常
    	*/
    	res = f_mount(&fsObject,"1:",1); //挂载文件系统,"1:"表示1号设备(0是SD_CARD),1表示立即挂载(默认即可)
    	printf("\r\n挂载状态(0:成功,其他:失败): res =%d\r\n\r\n",res);
    	
    	if(res == FR_NO_FILESYSTEM)//存储介质还没有格式化(类似于文件分配表和目录的初始结构)
    	{
    		//第一个参数1号存储介质,即路径
    		//第二个参数0表示使用usb、SD卡、flash等,如果用1表示软盘(现在基本不用了)
    		//第三个参数0表示自动分配簇的大小
    		//格式化会将存储介质上的数据全都擦除
    		res = f_mkfs("1:",0,0);//格式化存储介质,让存储介质具有一定的文件系统结构
    		printf("\r\n格式化状态(0:成功,其他:失败): res =%d",res);
    		//格式化后要取消挂载再重新挂载文件系统
    		res = f_mount(NULL,"1:",1);
    		res = f_mount(&fsObject,"1:",1);
    		printf("\r\n第二次挂载状态(0:成功,其他:失败): res =%d\r\n\r\n",res);
    	}
    	if(!res)
    	{
    		getDiskInfo();
    		printf("key_WAKE_UP:删除测试文件\tkey0:查看目录\tkey1:写入\tkey2:读出\r\n\r\n");
    		while(1)
    		{
    			key=KEY_Scan(0);
    			if(key==WKUP_PRES)
    			{
    				res = f_unlink("1:/FatFs读写测试文件.txt");		//删除某个文件
    				if(!res) printf("删除成功!\r\n");
    				else printf("删除失败!\r\n");
    			}
    			if(key==KEY0_PRES)
    			{
    				scan_files("1:");
    			}
    			if(key==KEY1_PRES)//按键1,写入文件
    			{
    				//FA_WRITE:以写的方式打开文件
    				//FA_OPEN_ALWAYS:如果文件不存在,则新建;如果存在,直接打开
    				res = f_open(&fp,"1:a.txt",FA_OPEN_ALWAYS|FA_WRITE);
    				if(res == FR_OK)
    				{
    					res = f_write(&fp,wData,sizeof(wData),&bw);
    					if(res == FR_OK)
    					{
    						printf ("\r\n写入文件:%s\t长度:%d\r\n",wData,bw);
    					}
    				}
    				f_close(&fp);
    			}
    			if(key==KEY2_PRES)//按键2,读取文件
    			{
    				res = f_open(&fp,"1:a.txt",FA_READ|FA_OPEN_ALWAYS);
    				if(res == FR_OK)
    				{
    					//f_size(&fp)读取整个文件的所有内容
    					res = f_read (&fp,rData,f_size(&fp),&br);
    					if(res == FR_OK)
    					{
    						printf ("\r\n读出文件:%s 长度= %d\r\n",rData,br);
    					}
    				}
    				f_close(&fp);
    			}
    			i++;
    			delay_ms(10);
    			if(i==20)
    			{
    				LED0 = !LED0;//提示系统正在运行	
    				i=0;
    			}
    		}
    	}
    	while(1){LED0 = 0;}
    }
    
    

      将上面的代码改完就可以烧录了,运行结果如下:

      上面的工程下载链接为:移植后工程2(FATFS基础功能测试).rar【提取码: 4z28】



     下面是打开文件时的一些模式:

     假如w25q128原有文件被破坏了,下面是不用SD卡进行恢复的方法(W25Q128恢复默认出厂文件,需要4.3寸电容触摸屏辅助,作为显示)

    • 测试文件是否被损坏:先下载这个工程文件,实验54 综合测试实验.rar【提取码: 59k8】 ,将USB线接入到USB SLAVE口(不是常用的USB 232),然后烧录到开发板,系统文件和字库文件都是存在 SPI FLASH(W25Q128)里面的,如这些文件被破坏了,在启动的时候,会提示 Font Error / SYSTEM File Error。
    • 根据提示,按 KEY1选择使用 USB 更新 SPI FLASH Files,等待系统提示:USB Connected 后,电脑会找到一个:ALIENTEK 的磁盘,然后将:光盘根目录-》SD 卡根目录文件-》SYSTEM 文件夹,拷贝SYSTEM 文件夹到 ALIENTEK 磁盘根目录。
    • 按 KEY0,此时系统自动重启,并自动更新所有文件(全自动,无需用户干预),等所有操作完成后,即可进入主界面。
    展开全文
  • 为了支持长文件名,需要用到FATFS...这两个对照表太大,太占用内部flash,所以,一般将它们做成一个.bin的文件,叫做UNIGBK.BIN,将这个文件烧录到外部FLASH中,然后需要转换的时候,读外部FLASH中的这个文件内容。 ...

     

    为了支持长文件名,需要用到FATFS源码中的cc936.c的两个函数ff_convert,ff_wtoupper;这里面直接用了两个大数组(127KB)来做unicode转gbk(OEM)的对照表,这两个对照表太大,太占用内部flash,所以,一般将它们做成一个.bin的文件,叫做UNIGBK.BIN,将这个文件烧录到外部FLASH中,然后需要转换的时候,读外部FLASH中的这个文件内容。

    下面是转自野火论坛的一篇FATFS的移植教材,有一定的参考价值。

     

    本章参考资料:《00index_e.html》,这是FatFs官方的编译好的HTML文档,里面有FatFs所有函数的介绍和函数的应用示例,学习FatFs看这个官方的文档即可。

    261 FatFs参考资料

    25.1 文件系统

    即使读者可能不了解文件系统,读者也一定对"文件"这个概念十分熟悉。数据在PC上是以文件的形式储存在磁盘中的,这些数据的形式一般为ASCII码或二进制形式。在上一章我们已经写好了SPI Flash芯片的驱动函数,我们可以非常方便的在SPI Flash芯片上读写数据。如需要记录本书的书名"零死角玩转STM32-F429系列",可以把这些文字转化成ASCII码,存储在数组中,然后调用SPI_FLASH_BufferWrite函数,把数组内容写入到SPI Flash芯片的指定地址上,在需要的时候从该地址把数据读取出来,再对读出来的数据以ASCII码的格式进行解读。

    但是,这样直接存储数据会带来极大的不便,如难以记录有效数据的位置,难以确定存储介质的剩余空间,以及应以何种格式来解读数据。就如同一个巨大的图书馆无人管理,杂乱无章地存放着各种书籍,难以查找所需的文档。想象一下图书馆的采购人员购书后,把书籍往馆内一扔,拍拍屁股走人,当有人来借阅某本书的时候,就不得不一本本地查找。这样直接存储数据的方式对于小容量的存储介质如EEPROM还可以接受,但对于SPI Flash芯片或者SD卡之类的大容量设备,我们需要一种高效的方式来管理它的存储内容。

    这些管理方式即为文件系统,它是为了存储和管理数据,而在存储介质建立的一种组织结构,这些结构包括操作系统引导区、目录和文件。常见的windows下的文件系统格式包括FAT32NTFSexFAT。在使用文件系统前,要先对存储介质进行格式化。格式化先擦除原来内容,在存储介质上新建一个文件分配表和目录。这样,文件系统就可以记录数据存放的物理地址,剩余空间。

    使用文件系统时,数据都以文件的形式存储。写入新文件时,先在目录中创建一个文件索引,它指示了文件存放的物理地址,再把数据存储到该地址中。当需要读取数据时,可以从目录中找到该文件的索引,进而在相应的地址中读取出数据。具体还涉及到逻辑地址、簇大小、不连续存储等一系列辅助结构或处理过程。

    文件系统的存在使我们在存取数据时,不再是简单地向某物理地址直接读写,而是要遵循它的读写格式。如经过逻辑转换,一个完整的文件可能被分开成多段存储到不连续的物理地址,使用目录或链表的方式来获知下一段的位置。

    上一章的SPI Flash芯片驱动只完成了向物理地址写入数据的工作,而根据文件系统格式的逻辑转换部分则需要额外的代码来完成。实质上,这个逻辑转换部分可以理解为当我们需要写入一段数据时,由它来求解向什么物理地址写入数据、以什么格式写入及写入一些原始数据以外的信息(如目录)。这个逻辑转换部分代码我们也习惯称之为文件系统。

    25.2 FatFs文件系统简介

    上面提到的逻辑转换部分代码(文件系统)即为本章的要点,文件系统庞大而复杂,它需要根据应用的文件系统格式而编写,而且一般与驱动层分离开来,很方便移植,所以工程应用中一般是移植现成的文件系统源码。

    FatFs是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由AISI C语言编写并且完全独立于底层的I/O介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如8051PICAVRSHZ80H8ARM等。FatFs支持FAT12FAT16FAT32等格式,所以我们利用前面写好的SPI Flash芯片驱动,把FatFs文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对SPI Flash芯片以"文件"格式进行读写操作了。

    FatFs文件系统的源码可以从fatfs官网下载:

    http://elm-chan.org/fsw/ff/00index_e.html

    25.2.1 FatFs的目录结构

    在移植FatFs文件系统到开发板之前,我们先要到FatFs的官网获取源码,最新版本为R0.11a,官网有对FatFs做详细的介绍,有兴趣可以了解。解压之后可看到里面有 doc src 这两个文件夹,见图 251doc 文件夹里面是一些使用帮助文档; src 才是FatFs文件系统的源码。

    251 FatFs文件目录

    25.2.2 FatFs帮助文档

    打开 doc 文件夹,可看到如图 252的文件目录:

    252 doc文件夹的文件目录

    其中 en ja 这两个文件夹里面是编译好的html文档,讲的是FATFS里面各个函数的使用方法,这些函数都是封装得非常好的函数,利用这些函数我们就可以操作SPI Flash芯片。有关具体的函数我们在用到的时候再讲解。这两个文件夹的唯一区别就是 en 文件夹下的文档是英文的,ja 文件夹下的是日文的。img文件夹包含enja文件夹下文件需要用到的图片,还有四个名为app.c文件,内容都是FatFs具体应用例程。00index_e.html00index_j.html是一些关于FATFS的简介,至于另外两个文件可以不看。

    25.2.3 FATFS源码

    打开 src 文件夹,可看到如图 253的文件目录:

    253 src文件夹的文件目录

    option 文件夹下是一些可选的外部c文件,包含了多语言支持需要用到的文件和转换函数。

    diskio.c文件是FatFs移植最关键的文件,它为文件系统提供了最底层的访问SPI Flash芯片的方法,FatFs有且仅有它需要用到与SPI Flash芯片相关的函数。diskio.h定义了FatFs用到的宏,以及diskio.c文件内与底层硬件接口相关的函数声明。

    00history.txt介绍了FatFs的版本更新情况。

    00readme.txt说明了当前目录下 diskio.c diskio.hff.cff.hinteger.h的功能。

    src文件夹下的源码文件功能简介如下:

        integer.h:文件中包含了一些数值类型定义。

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

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

        cc936.c:本文件在option目录下,是简体中文支持所需要添加的文件,包含了简体中文的GBK和Unicode相互转换功能函数。

        ffconf.h:这个头文件包含了对FatFs功能配置的宏定义,通过修改这些宏定义就可以裁剪FatFs的功能。如需要支持简体中文,需要把ffconf.h中的_CODE_PAGE 的宏改成936并把上面的cc936.c文件加入到工程之中。

    建议阅读这些源码的顺序为:integer.h --> diskio.c --> ff.c

    阅读文件系统源码ff.c文件需要一定的功底,建议读者先阅读FAT32的文件格式,再去分析ff.c文件。若仅为使用文件系统,则只需要理解integer.hdiskio.c文件并会调用ff.c文件中的函数就可以了。本章主要讲解如何把FATFS文件系统移植到开发板上,并编写一个简单读写操作范例。

    25.3 FatFs文件系统移植实验

    25.3.1 FatFs程序结构图

    移植FatFs之前我们先通过FatFs的程序结构图了解FatFs在程序中的关系网络,见图 254

    254 FatFs程序结构图

    用户应用程序需要由用户编写,想实现什么功能就编写什么的程序,一般我们只用到f_mount()f_open()f_write()f_read()就可以实现文件的读写操作。

    FatFs组件是FatFs的主体,文件都在源码src文件夹中,其中ff.cff.hinteger.h以及diskio.h四个文件我们不需要改动,只需要修改ffconf.hdiskio.c两个文件。

    底层设备输入输出要求实现存储设备的读写操作函数、存储设备信息获取函数等等。我们使用SPI Flash芯片作为物理设备,在上一章节已经编写好了SPI Flash芯片的驱动程序,这里我们就直接使用。

    25.3.2 硬件设计

    FatFs属于软件组件,不需要附带其他硬件电路。我们使用SPI Flash芯片作为物理存储设备,其硬件电路在上一章已经做了分析,这里就直接使用。

    25.3.3 FatFs移植步骤

    上一章我们已经实现了SPI Flash芯片驱动程序,并实现了读写测试,为移植FatFs方便,我们直接拷贝一份工程,我们在工程基础上添加FatFs组件,并修改main函数的用户程序即可。

    1)    先拷贝一份SPI Flash芯片测试的工程文件(整个文件夹),并修改文件夹名为"SPI—FatFs文件系统"。将FatFs源码中的src文件夹整个文件夹拷贝一份至"SPI—FatFs文件系统\USER\"文件夹下并修改名为"FATFS",见图 255。

    255 拷贝FatFs源码到工程

    2)    使用KEIL软件打开工程文件(..\SPI—FatFs文件系统\Project\RVMDK(uv5)\ BH-F429.uvprojx),并将FatFs组件文件添加到工程中,需要添加有ff.c、diskio.c和cc936.c三个文件,见图 256。

    256 添加FatFS文件到工程

    3)    添加FATFS文件夹到工程的include选项中。打开工程选项对话框,选择"C/C++"选项下的"Include Paths"项目,在弹出路径设置对话框中选择添加"FATFS"文件夹,见图 257。

    257 添加FATFS路径到工程选项

    4)    如果现在编译工程,可以发现有两个错误,一个是来自diskio.c文件,提示有一些头文件没找,diskio.c文件内容是与底层设备输入输出接口函数文件,不同硬件设计驱动就不同,需要的文件也不同;另外一个错误来自cc936.c文件,提示该文件不是工程所必需的,这是因为FatFs默认使用日语,我们想要支持简体中文需要修改FatFs的配置,即修改ffconf.h文件。至此,将FatFs添加到工程的框架已经操作完成,接下来要做的就是修改diskio.c文件和ffconf.h文件。

    25.3.4 FatFs底层设备驱动函数

    FatFs文件系统与底层介质的驱动分离开来,对底层介质的操作都要交给用户去实现,它仅仅是提供了一个函数接口而已。表 251FatFs移植时用户必须支持的函数。通过表 251我们可以清晰知道很多函数是在一定条件下才需要添加的,只有前三个函数是必须添加的。我们完全可以根据实际需求选择实现用到的函数。

    前三个函数是实现读文件最基本需求。接下来三个函数是实现创建文件、修改文件需要的。为实现格式化功能,需要在disk_ioctl添加两个获取物理设备信息选项。我们一般只有实现前面六个函数就可以了,已经足够满足大部分功能。

    为支持简体中文长文件名称需要添加ff_convert和ff_wtoupper函数,实际这两个已经在cc936.c文件中实现了,我们只要直接把cc936.c文件添加到工程中就可以了。

    后面六个函数一般都不用。如真有需要可以参考syscall.c文件(src\option文件夹内)。

    251 FatFs移植需要用户支持函数

    函数

    条件(ffconf.h)

    备注

    disk_status
    disk_initialize
    disk_read

    总是需要

    底层设备驱动函数

    disk_write
    get_fattime
    disk_ioctl (CTRL_SYNC)

    _FS_READONLY == 0

    disk_ioctl (GET_SECTOR_COUNT)
    disk_ioctl (GET_BLOCK_SIZE)

    _USE_MKFS == 1

    disk_ioctl (GET_SECTOR_SIZE)

    _MAX_SS != _MIN_SS

    disk_ioctl (CTRL_TRIM)

    _USE_TRIM == 1

    ff_convert
    ff_wtoupper

    _USE_LFN != 0

    Unicode支持,为支持简体中文,添加cc936.c到工程即可

    ff_cre_syncobj
    ff_del_syncobj
    ff_req_grant
    ff_rel_grant

    _FS_REENTRANT == 1

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

    ff_mem_alloc
    ff_mem_free

    _USE_LFN == 3

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

    底层设备驱动函数是存放在diskio.c文件,我们的目的就是把diskio.c中的函数接口与SPI Flash芯片驱动连接起来。总共有五个函数,分别为设备状态获取(disk_status)、设备初始化(disk_initialize)、扇区读取(disk_read)、扇区写入(disk_write)、其他控制(disk_ioctl)

    接下来,我们对每个函数结合SPI Flash芯片驱动做详细讲解。

    宏定义

    代码清单 251 物理编号宏定义

    1 /* 为每个设备定义一个物理编号 */

    2 #define ATA 0 // 预留SD卡使用

    3 #define SPI_FLASH 1 // 外部SPI Flash

    这两个宏定义在FatFs中非常重要,FatFs是支持多物理设备的,必须为每个物理设备定义一个不同的编号。

    SD卡是预留接口,在讲解SDIO接口相关章节后会用到,可以实现使用读写SD卡内文件。

    设备状态获取

    代码清单 252设备状态获取

    1 DSTATUS disk_status (

    2 BYTE pdrv /* 物理编号 */

    3 )

    4 {

    5

    6 DSTATUS status = STA_NOINIT;

    7

    8 switch (pdrv) {

    9 case ATA: /* SD CARD */

    10 break;

    11

    12 case SPI_FLASH:

    13 /* SPI Flash状态检测:读取SPI Flash 设备ID */

    14 if (sFLASH_ID == SPI_FLASH_ReadID()) {

    15 /* 设备ID读取结果正确 */

    16 status &= ~STA_NOINIT;

    17 } else {

    18 /* 设备ID读取结果错误 */

    19 status = STA_NOINIT;;

    20 }

    21 break;

    22

    23 default:

    24 status = STA_NOINIT;

    25 }

    26 return status;

    27 }

    disk_status函数只有一个参数pdrv,表示物理编号。一般我们都是使用switch函数实现对pdrv的分支判断。对于SD卡只是预留接口,留空即可。对于SPI Flash芯片,我们直接调用在SPI_FLASH_ReadID()获取设备ID,然后判断是否正确,如果正确,函数返回正常标准;如果错误,函数返回异常标志。SPI_FLASH_ReadID()是定义在bsp_spi_flash.c文件中,上一章节已做了分析。

    设备初始化

    代码清单 253 设备初始化

    1 DSTATUS disk_initialize (

    2 BYTE pdrv /* 物理编号 */

    3 )

    4 {

    5 uint16_t i;

    6 DSTATUS status = STA_NOINIT;

    7 switch (pdrv) {

    8 case ATA: /* SD CARD */

    9 break;

    10

    11 case SPI_FLASH: /* SPI Flash */

    12 /* 初始化SPI Flash */

    13 SPI_FLASH_Init();

    14 /* 延时一小段时间 */

    15 i=500;

    16 while (--i);

    17 /* 唤醒SPI Flash */

    18 SPI_Flash_WAKEUP();

    19 /* 获取SPI Flash芯片状态 */

    20 status=disk_status(SPI_FLASH);

    21 break;

    22

    23 default:

    24 status = STA_NOINIT;

    25 }

    26 return status;

    27 }

    disk_initialize函数也是有一个参数pdrv,用来指定设备物理编号。对于SPI Flash芯片我们调用SPI_FLASH_Init()函数实现对SPI Flash芯片引脚GPIO初始化配置以及SPI通信参数配置。SPI_Flash_WAKEUP()函数唤醒SPI Flash芯片,当SPI Flash芯片处于睡眠模式时需要唤醒芯片才可以进行读写操作。

    最后调用disk_status函数获取SPI Flash芯片状态,并返回状态值。

    读取扇区

    代码清单 254 扇区读取

    1 DRESULT disk_read (

    2 BYTE pdrv, /* 设备物理编号(0..) */

    3 BYTE *buff, /* 数据缓存区 */

    4 DWORD sector, /* 扇区首地址 */

    5 UINT count /* 扇区个数(1..128) */

    6 )

    7 {

    8 DRESULT status = RES_PARERR;

    9 switch (pdrv) {

    10 case ATA: /* SD CARD */

    11 break;

    12

    13 case SPI_FLASH:

    14 /* 扇区偏移6MB,外部Flash文件系统空间放在SPI Flash后面10MB空间 */

    15 sector+=1536;

    16 SPI_FLASH_BufferRead(buff, sector <<12, count<<12);

    17 status = RES_OK;

    18 break;

    19

    20 default:

    21 status = RES_PARERR;

    22 }

    23 return status;

    24 }

    disk_read函数有四个形参。pdrv为设备物理编号。buff是一个BYTE类型指针变量,buff指向用来存放读取到数据的存储区首地址。sector是一个DWORD类型变量,指定要读取数据的扇区首地址。count是一个UINT类型变量,指定扇区数量。

    BYTE类型实际是unsigned char类型,DWORD类型实际是unsigned long类型,UINT类型实际是 unsigned int类型,类型定义在integer.h文件中。

    开发板使用的SPI Flash芯片型号为W25Q128FV,每个扇区大小为4096个字节(4KB),总共有16M字节空间,为兼容后面实验程序,我们只将后部分10MB空间分配给FatFs使用,前部分6MB空间用于其他实验需要,即FatFs是从6MB空间开始,为实现这个效果需要将所有的读写地址都偏移1536个扇区空间。

    对于SPI Flash芯片,主要是使用SPI_FLASH_BufferRead()实现在指定地址读取指定长度的数据,它接收三个参数,第一个参数为指定数据存放地址指针。第二个参数为指定数据读取地址,这里使用左移运算符,左移12位实际是乘以4096,这与每个扇区大小是息息相关的。第三个参数为读取数据个数,也是需要使用左移运算符。

    扇区写入

    代码清单 255 扇区输入

    1 DRESULT disk_write (

    2 BYTE pdrv, /* 设备物理编号(0..) */

    3 const BYTE *buff, /* 欲写入数据的缓存区 */

    4 DWORD sector, /* 扇区首地址 */

    5 UINT count /* 扇区个数(1..128) */

    6 )

    7 {

    8 uint32_t write_addr;

    9 DRESULT status = RES_PARERR;

    10 if (!count) {

    11 return RES_PARERR; /* Check parameter */

    12 }

    13

    14 switch (pdrv) {

    15 case ATA: /* SD CARD */

    16 break;

    17

    18 case SPI_FLASH:

    19 /* 扇区偏移6MB,外部Flash文件系统空间放在SPI Flash后面10MB空间 */

    20 sector+=1536;

    21 write_addr = sector<<12;

    22 SPI_FLASH_SectorErase(write_addr);

    23 SPI_FLASH_BufferWrite((u8 *)buff,write_addr,count<<12);

    24 status = RES_OK;

    25 break;

    26

    27 default:

    28 status = RES_PARERR;

    29 }

    30 return status;

    31 }

    disk_write函数有四个形参,pdrv为设备物理编号。buff指向待写入扇区数据的首地址。sector,指定要读取数据的扇区首地址。count指定扇区数量。对于SPI Flash芯片,在写入数据之前需要先擦除,所以用到扇区擦除函数(SPI_FLASH_SectorErase)。然后就是在调用数据写入函数(SPI_FLASH_BufferWrite)把数据写入到指定位置内。

    其他控制

    代码清单 256 其他控制

    1 DRESULT disk_ioctl (

    2 BYTE pdrv, /* 物理编号 */

    3 BYTE cmd, /* 控制指令 */

    4 void *buff /* 写入或者读取数据地址指针 */

    5 )

    6 {

    7 DRESULT status = RES_PARERR;

    8 switch (pdrv) {

    9 case ATA: /* SD CARD */

    10 break;

    11

    12 case SPI_FLASH:

    13 switch (cmd) {

    14 /* 扇区数量:2560*4096/1024/1024=10(MB) */

    15 case GET_SECTOR_COUNT:

    16 *(DWORD * )buff = 2560;

    17 break;

    18 /* 扇区大小 */

    19 case GET_SECTOR_SIZE :

    20 *(WORD * )buff = 4096;

    21 break;

    22 /* 同时擦除扇区个数 */

    23 case GET_BLOCK_SIZE :

    24 *(DWORD * )buff = 1;

    25 break;

    26 }

    27 status = RES_OK;

    28 break;

    29

    30 default:

    31 status = RES_PARERR;

    32 }

    33 return status;

    34 }

    disk_ioctl函数有三个形参,pdrv为设备物理编号,cmd为控制指令,包括发出同步信号、获取扇区数目、获取扇区大小、获取擦除块数量等等指令,buff为指令对应的数据指针。

    对于SPI Flash芯片,为支持FatFs格式化功能,需要用到获取扇区数量(GET_SECTOR_COUNT)指令和获取擦除块数量(GET_BLOCK_SIZE)。另外,SD卡扇区大小为512字节,SPI Flash芯片一般设置扇区大小为4096字节,所以需要用到获取扇区大小(GET_SECTOR_SIZE)指令。

    时间戳获取

    代码清单 257 时间戳获取

    1 __weak DWORD get_fattime(void)

    2 {

    3 /* 返回当前时间戳 */

    4 return ((DWORD)(2015 - 1980) << 25) /* Year 2015 */

    5 | ((DWORD)1 << 21) /* Month 1 */

    6 | ((DWORD)1 << 16) /* Mday 1 */

    7 | ((DWORD)0 << 11) /* Hour 0 */

    8 | ((DWORD)0 << 5) /* Min 0 */

    9 | ((DWORD)0 >> 1); /* Sec 0 */

    10 }

    get_fattime函数用于获取当前时间戳,在ff.c文件中被调用。FatFs在文件创建、被修改时会记录时间,这里我们直接使用赋值方法设定时间戳。为更好的记录时间,可以使用控制器RTC功能,具体要求返回值格式为:

        bit31:25 ——从1980至今是多少年,范围是 (0..127) ;

        bit24:21 ——月份,范围为 (1..12) ;

        bit20:16 ——该月份中的第几日,范围为(1..31) ;

        bit15:11——时,范围为 (0..23);

         bit10:5 ——分,范围为 (0..59);

         bit4:0 ——秒/ 2,范围为 (0..29) 。

    25.3.5 FatFs功能配置

    ffconf.h文件是FatFs功能配置文件,我们可以对文件内容进行修改,使得FatFs更符合我们的要求。ffconf.h对每个配置选项都做了详细的使用情况说明。下面只列出修改的配置,其他配置采用默认即可。

    代码清单 258 FatFs功能配置选项

    1 #define _USE_MKFS 1

    2 #define _CODE_PAGE 936

    3 #define _USE_LFN 2

    4 #define _VOLUMES 2

    5 #define _MIN_SS 512

    6 #define _MAX_SS 4096

    1)    _USE_MKFS:格式化功能选择,为使用FatFs格式化功能,需要把它设置为1。

    2)    _CODE_PAGE:语言功能选择,并要求把相关语言文件添加到工程宏。为支持简体中文文件名需要使用"936",正如在图 256的操作,我们已经把cc936.c文件添加到工程中。

    3)    _USE_LFN:长文件名支持,默认不支持长文件名,这里配置为2,支持长文件名,并指定使用栈空间为缓冲区。

    4)    _VOLUMES:指定物理设备数量,这里设置为2,包括预留SD卡和SPI Flash芯片。

    5)    _MIN_SS 、_MAX_SS:指定扇区大小的最小值和最大值。SD卡扇区大小一般都为512字节,SPI Flash芯片扇区大小一般设置为4096字节,所以需要把_MAX_SS改为4096。

    25.3.6 FatFs功能测试

    移植操作到此,就已经把FatFs全部添加到我们的工程了,这时我们编译功能,顺利编译通过,没有错误。接下来,我们就可以使用编写图 254中用户应用程序了。

    主要的测试包括格式化测试、文件写入测试和文件读取测试三个部分,主要程序都在main.c文件中实现。

    变量定义

    代码清单 259 变量定义

    1 FATFS fs; /* FatFs文件系统对象 */

    2 FIL fnew; /* 文件对象 */

    3 FRESULT res_flash; /* 文件操作结果 */

    4 UINT fnum; /* 文件成功读写数量 */

    5 BYTE buffer[1024]= {0}; /* 读缓冲区 */

    6 BYTE textFileBuffer[] = /* 写缓冲区*/

    7 "欢迎使用野火STM32 F429开发板今天是个好日子,新建文件系统测试文件\r\n";

    FATFS是在ff.h文件定义的一个结构体类型,针对的对象是物理设备,包含了物理设备的物理编号、扇区大小等等信息,一般我们都需要为每个物理设备定义一个FATFS变量。

    FIL也是在ff.h文件定义的一个结构体类型,针对的对象是文件系统内具体的文件,包含了文件很多基本属性,比如文件大小、路径、当前读写地址等等。如果需要在同一时间打开多个文件进行读写,才需要定义多个FIL变量,不然一般定义一个FIL变量即可。

    FRESULT是也在ff.h文件定义的一个枚举类型,作为FatFs函数的返回值类型,主要管理FatFs运行中出现的错误。总共有19种错误类型,包括物理设备读写错误、找不到文件、没有挂载工作空间等等错误。这在实际编程中非常重要,当有错误出现是我们要停止文件读写,通过返回值我们可以快速定位到错误发生的可能地点。如果运行没有错误才返回FR_OK

    fnum是个32位无符号整形变量,用来记录实际读取或者写入数据的数组。

    buffertextFileBuffer分别对应读取和写入数据缓存区,都是8位无符号整形数组。

    主函数

    代码清单 2510 主函数

    1 int main(void)

    2 {

    3 /* 初始化LED */

    4 LED_GPIO_Config();

    5 LED_BLUE;

    6

    7 /* 初始化调试串口,一般为串口1 */

    8 Debug_USART_Config();

    9 printf("****** 这是一个SPI FLASH 文件系统实验 ******\r\n");

    10

    11 //在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化

    12 res_flash = f_mount(&fs,"1:",1);

    13

    14 /*----------------------- 格式化测试 ---------------------------*/

    15 /* 如果没有文件系统就格式化创建创建文件系统 */

    16 if (res_flash == FR_NO_FILESYSTEM) {

    17 printf("FLASH还没有文件系统,即将进行格式化...\r\n");

    18 /* 格式化 */

    19 res_flash=f_mkfs("1:",0,0);

    20

    21 if (res_flash == FR_OK) {

    22 printf("FLASH已成功格式化文件系统。\r\n");

    23 /* 格式化后,先取消挂载 */

    24 res_flash = f_mount(NULL,"1:",1);

    25 /* 重新挂载 */

    26 res_flash = f_mount(&fs,"1:",1);

    27 } else {

    28 LED_RED;

    29 printf("《《格式化失败。》》\r\n");

    30 while (1);

    31 }

    32 } else if (res_flash!=FR_OK) {

    33 printf("!!外部Flash挂载文件系统失败。(%d)\r\n",res_flash);

    34 printf("!!可能原因:SPI Flash初始化不成功。\r\n");

    35 while (1);

    36 } else {

    37 printf("》文件系统挂载成功,可以进行读写测试\r\n");

    38 }

    39

    40 /*---------------------- 文件系统测试:写测试 ----------------------*/

    41 /* 打开文件,如果文件不存在则创建它 */

    42 printf("\r\n****** 即将进行文件写入测试... ******\r\n");

    43 res_flash = f_open(&fnew, "1:FatFs读写测试文件.txt",

    44 FA_CREATE_ALWAYS | FA_WRITE );

    45 if ( res_flash == FR_OK ) {

    46 printf("》打开/创建FatFs读写测试文件.txt文件成功,向文件写入数据。\r\n");

    47 /* 将指定存储区内容写入到文件内 */

    48 res_flash=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);

    49 if (res_flash==FR_OK) {

    50 printf("》文件写入成功,写入字节数据:%d\n",fnum);

    51 printf("》向文件写入的数据为:\r\n%s\r\n",WriteBuffer);

    52 } else {

    53 printf("!!文件写入失败:(%d)\n",res_flash);

    54 }

    55 /* 不再读写,关闭文件 */

    56 f_close(&fnew);

    57 } else {

    58 LED_RED;

    59 printf("!!打开/创建文件失败。\r\n");

    60 }

    61

    62 /*------------------- 文件系统测试:读测试 --------------------------*/

    63 printf("****** 即将进行文件读取测试... ******\r\n");

    64 res_flash = f_open(&fnew, "1:FatFs读写测试文件.txt",

    65 FA_OPEN_EXISTING | FA_READ);

    66 if (res_flash == FR_OK) {

    67 LED_GREEN;

    68 printf("》打开文件成功。\r\n");

    69 res_flash = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);

    70 if (res_flash==FR_OK) {

    71 printf("》文件读取成功,读到字节数据:%d\r\n",fnum);

    72 printf("》读取得的文件数据为:\r\n%s \r\n", ReadBuffer);

    73 } else {

    74 printf("!!文件读取失败:(%d)\n",res_flash);

    75 }

    76 } else {

    77 LED_RED;

    78 printf("!!打开文件失败。\r\n");

    79 }

    80 /* 不再读写,关闭文件 */

    81 f_close(&fnew);

    82

    83 /* 不再使用文件系统,取消挂载文件系统 */

    84 f_mount(NULL,"1:",1);

    85

    86 /* 操作完成,停机 */

    87 while (1) {

    88 }

    89 }

    首先,初始化RGB彩灯和调试串口,用来指示程序进程。

    FatFs的第一步工作就是使用f_mount函数挂载工作区。f_mount函数有三个形参,第一个参数是指向FATFS变量指针,如果赋值为NULL可以取消物理设备挂载。第二个参数为逻辑设备编号,使用设备根路径表示,与物理设备编号挂钩,在代码清单 251中我们定义SPI Flash芯片物理编号为1,所以这里使用"1:"。第三个参数可选011表示立即挂载,0表示不立即挂载,延迟挂载。 f_mount函数会返回一个FRESULT类型值,指示运行情况。

    如果f_mount函数返回值为FR_NO_FILESYSTEM,说明没有FAT文件系统,比如新出厂的SPI Flash芯片就没有FAT文件系统。我们就必须对物理设备进行格式化处理。使用f_mkfs函数可以实现格式化操作。f_mkfs函数有三个形参,第一个参数为逻辑设备编号;第二参数可选0或者10表示设备为一般硬盘,1表示设备为软盘。第三个参数指定扇区大小,如果为0,表示通过代码清单 256disk_ioctl函数获取。格式化成功后需要先取消挂载原来设备,再重新挂载设备。

    在设备正常挂载后,就可以进行文件读写操作了。使用文件之前,必须使用f_open函数打开文件,不再使用文件必须使用f_close函数关闭文件,这个跟电脑端操作文件步骤类似。f_open函数有三个形参,第一个参数为文件对象指针。第二参数为目标文件,包含绝对路径的文件名称和后缀名。第三个参数为访问文件模式选择,可以是打开已经存在的文件模式、读模式、写模式、新建模式、总是新建模式等的或运行结果。比如对于写测试,使用FA_CREATE_ALWAYSFA_WRITE组合模式,就是总是新建文件并进行写模式。

    f_close函数用于不再对文件进行读写操作关闭文件,f_close函数只要一个形参,为文件对象指针。f_close函数运行可以确保缓冲区完全写入到文件内。

    成功打开文件之后就可以使用f_write函数和f_read函数对文件进行写操作和读操作。这两个函数用到的参数是一致的,只不过一个是数据写入,一个是数据读取。f_write函数第一个形参为文件对象指针,使用与f_open函数一致即可。第二个参数为待写入数据的首地址,对于f_read函数就是用来存放读出数据的首地址。第三个参数为写入数据的字节数,对于f_read函数就是欲读取数据的字节数。第四个参数为32位无符号整形指针,这里使用fnum变量地址赋值给它,在运行读写操作函数后,fnum变量指示成功读取或者写入的字节个数。

    最后,不再使用文件系统时,使用f_mount函数取消挂载。

    25.3.7 下载验证

    保证开发板相关硬件连接正确,用USB线连接开发板"USB TO UART"接口跟电脑,在电脑端打开串口调试助手,把编译好的程序下载到开发板。程序开始运行后,RGB彩灯为蓝色,在串口调试助手可看到格式化测试、写文件检测和读文件检测三个过程;最后如果所有读写操作都正常,RGB彩灯会指示为绿色,如果在运行中FatFs出现错误RGB彩灯指示为红色。

    虽然我们通过RGB彩灯指示和串口调试助手信息打印方法来说明FatFs移植成功,并顺利通过测试,但心底总是很踏实,所谓眼见为实,虽然我们创建了"FatFs读写测试文件.txt"这个文件,却完全看不到实体。这个确实是个问题,因为我们这里使用SPI Flash芯片作为物理设备,并不像SD卡那么方便直接用读卡器就可以在电脑端打开验证。另外一个问题,就目前来说,在SPI Flash芯片上挂载FatFs好像没有实际意义,无法发挥文件系统功能。

    实际上,这里归根到底就是我们目前没办法在电脑端查看SPI Flash芯片内FatFs的内容,没办法非常方便拷贝、删除文件。我们当然不会做无用功,STM32控制器还有一个硬件资源可以解决上面的问题,就是USB!我们可以通过编程把整个开发板变成一个U盘,而U盘存储空间就是SPI Flash芯片的空间。这样非常方便实现文件读写。至于USB内容将在USB相关章节讲解。

    25.4 FatFs功能使用实验

    上个实验我们实现了FatFs的格式化、读文件和写文件功能,这个已经满足很多部分的运用需要。有时,我们需要更多的文件操作功能,FatFs还是提供了不少的功能的,比如设备存储空间信息获取、读写文件指针定位、创建目录、文件移动和重命名、文件或目录信息获取等等功能。我们接下来这个实验内容就是展示FatFs众多功能,提供一个很好了范例,以后有用到相关内容,参考使用非常方便。

    25.4.1 硬件设计

    本实验主要使用FatFs软件功能,不需要其他硬件模块,使用与FatFs移植实验相同硬件配置即可。

    25.4.2 软件设计

    上个实验我们已经移植好了FatFs,这个例程主要是应用,所以简单起见,直接拷贝上个实验的工程文件,保持FatFs底层驱动程序,我们只改main.c文件内容,实现应用程序。

    FatFs多项功能测试

    代码清单 2511 FatFs多项功能测试

    1 static FRESULT miscellaneous(void)

    2 {

    3 DIR dir;

    4 FATFS *pfs;

    5 DWORD fre_clust, fre_sect, tot_sect;

    6

    7 printf("\n*************** 设备信息获取 ***************\r\n");

    8 /* 获取设备信息和空簇大小 */

    9 res_flash = f_getfree("1:", &fre_clust, &pfs);

    10

    11 /* 计算得到总的扇区个数和空扇区个数 */

    12 tot_sect = (pfs->n_fatent - 2) * pfs->csize;

    13 fre_sect = fre_clust * pfs->csize;

    14

    15 /* 打印信息(4096 字节/扇区) */

    16 printf("》设备总空间:%10lu KB\n》可用空间: %10lu KB\n",

    17 tot_sect *4, fre_sect *4);

    18

    19 printf("\n******** 文件定位和格式化写入功能测试 ********\r\n");

    20 res_flash = f_open(&fnew, "1:FatFs读写测试文件.txt",

    21 FA_OPEN_EXISTING|FA_WRITE|FA_READ );

    22 if ( res_flash == FR_OK ) {

    23 /* 文件定位 */

    24 res_flash = f_lseek(&fnew,f_size(&fnew)-1);

    25 if (res_flash == FR_OK) {

    26 /* 格式化写入,参数格式类似printf函数 */

    27 f_printf(&fnew,"\n在原来文件新添加一行内容\n");

    28 f_printf(&fnew,"》设备总空间:%10lu KB\n》可用空间: %10lu KB\n",

    29 tot_sect *4, fre_sect *4);

    30 /* 文件定位到文件起始位置 */

    31 res_flash = f_lseek(&fnew,0);

    32 /* 读取文件所有内容到缓存区 */

    33 res_flash = f_read(&fnew,readbuffer,f_size(&fnew),&fnum);

    34 if (res_flash == FR_OK) {

    35 printf("》文件内容:\n%s\n",readbuffer);

    36 }

    37 }

    38 f_close(&fnew);

    39

    40 printf("\n********** 目录创建和重命名功能测试 **********\r\n");

    41 /* 尝试打开目录 */

    42 res_flash=f_opendir(&dir,"1:TestDir");

    43 if (res_flash!=FR_OK) {

    44 /* 打开目录失败,就创建目录 */

    45 res_flash=f_mkdir("1:TestDir");

    46 } else {

    47 /* 如果目录已经存在,关闭它 */

    48 res_flash=f_closedir(&dir);

    49 /* 删除文件 */

    50 f_unlink("1:TestDir/testdir.txt");

    51 }

    52 if (res_flash==FR_OK) {

    53 /* 重命名并移动文件 */

    54 res_flash=f_rename("1:FatFs读写测试文件.txt",

    55 "1:TestDir/testdir.txt");

    56 }

    57 } else {

    58 printf("!! 打开文件失败:%d\n",res_flash);

    59 printf("!! 或许需要再次运行"FatFs移植与读写测试"工程\n");

    60 }

    61 return res_flash;

    62 }

    首先是设备存储信息获取,目的是获取设备总容量和剩余可用空间。f_getfree函数是设备空闲簇信息获取函数,有三个形参,第一个参数为逻辑设备编号;第二个参数为返回空闲簇数量;第三个参数为返回指向文件系统对象的指针。通过计算可得到设备总的扇区个数以及空闲扇区个数,对于SPI Flash芯片我们设置每个扇区为4096字节大小。这样很容易就算出设备存储信息。

    接下来是文件读写指针定位和格式化输入功能测试。文件定位在一些场合非常有用,比如我们需要记录多项数据,但每项数据长度不确定,但有个最长长度,使用我们就可以使用文件定位lseek函数功能把数据存放在规定好的地址空间上。当我们需要读取文件内容时就使用文件定位函数定位到对应地址读取。

    使用文件读写操作之前都必须使用f_open函数打开文件,开始文件是读写指针是在文件起始位置的,马上写入数据的话会覆盖原来文件内容的。这里,我们使用f_lseek函数定位到文件末尾位置,再写入内容。f_lseek函数有两个形参,第一个参数为文件对象指针,第二个参数为需要定位的字节数,这个字节数是相对文件起始位置的,比如设置为0,则将文件读写指针定位到文件起始位置了。

    f_printf函数是格式化写入函数,需要把ffconf.h文件中的_USE_STRFUNC配置为1才支持。f_printf函数用法类似C库函数printf函数,只是它将数据直接写入到文件中。

    最后是目录创建和文件移动和重命名功能。使用f_opendir函数可以打开路径(这里不区分目录和路径概念,下同),如果路径不存在则返回错误,使用f_closedir函数关闭已经打开的路径。新版的FatFs支持相对路径功能,使路径操作更加灵活。f_opendir函数有两个形参,第一个参数为指向路径对象的指针,第二个参数为路径。f_closedir函数只需要指向路径对象的指针一个形参。

    f_mkdir函数用于创建路径,如果指定的路径不存在就创建它,创建的路径存在形式就是文件夹。f_mkdir函数只要一个形参,就是指定路径。

    f_rename函数是带有移动功能的重命名函数,它有两个形参,第一个参数为源文件名称,第二个参数为目标名称。目标名称可附带路径,如果路径与源文件路径不同见移动文件到目标路径下。

    文件信息获取

    代码清单 2512 文件信息获取

    1 static FRESULT file_check(void)

    2 {

    3 FILINFO fno;

    4

    5 /* 获取文件信息 */

    6 res_flash=f_stat("1:TestDir/testdir.txt",&fno);

    7 if (res_flash==FR_OK) {

    8 printf(""testdir.txt"文件信息:\n");

    9 printf("》文件大小: %ld(字节)\n", fno.fsize);

    10 printf("》时间戳: %u/%02u/%02u, %02u:%02u\n",

    11 (fno.fdate >> 9) + 1980, fno.fdate >> 5 & 15, fno.fdate & 31,

    12 fno.ftime >> 11, fno.ftime >> 5 & 63);

    13 printf("》属性: %c%c%c%c%c\n\n",

    14 (fno.fattrib & AM_DIR) ? 'D' : '-', // 是一个目录

    15 (fno.fattrib & AM_RDO) ? 'R' : '-', // 只读文件

    16 (fno.fattrib & AM_HID) ? 'H' : '-', // 隐藏文件

    17 (fno.fattrib & AM_SYS) ? 'S' : '-', // 系统文件

    18 (fno.fattrib & AM_ARC) ? 'A' : '-'); // 档案文件

    19 }

    20 return res_flash;

    21 }

    f_stat函数用于获取文件的属性,有两个形参,第一个参数为文件路径,第二个参数为返回指向文件信息结构体变量的指针。文件信息结构体变量包含文件的大小、最后修改时间和日期、文件属性、短文件名以及长文件名等信息。

    路径扫描

    代码清单 2513 路径扫描

    1 static FRESULT scan_files (char* path)

    2 {

    3 FRESULT res; //部分在递归过程被修改的变量,不用全局变量

    4 FILINFO fno;

    5 DIR dir;

    6 int i;

    7 char *fn; // 文件名

    8

    9 #if _USE_LFN

    10 /* 长文件名支持 */

    11 /* 简体中文需要2个字节保存一个""*/

    12 static char lfn[_MAX_LFN*2 + 1];

    13 fno.lfname = lfn;

    14 fno.lfsize = sizeof(lfn);

    15 #endif

    16 //打开目录

    17 res = f_opendir(&dir, path);

    18 if (res == FR_OK) {

    19 i = strlen(path);

    20 for (;;) {

    21 //读取目录下的内容,再读会自动读下一个文件

    22 res = f_readdir(&dir, &fno);

    23 //为空时表示所有项目读取完毕,跳出

    24 if (res != FR_OK || fno.fname[0] == 0) break;

    25 #if _USE_LFN

    26 fn = *fno.lfname ? fno.lfname : fno.fname;

    27 #else

    28 fn = fno.fname;

    29 #endif

    30 //点表示当前目录,跳过

    31 if (*fn == '.') continue;

    32 //目录,递归读取

    33 if (fno.fattrib & AM_DIR) {

    34 //合成完整目录名

    35 sprintf(&path[i], "/%s", fn);

    36 //递归遍历

    37 res = scan_files(path);

    38 path[i] = 0;

    39 //打开失败,跳出循环

    40 if (res != FR_OK)

    41 break;

    42 } else {

    43 printf("%s/%s\r\n", path, fn); //输出文件名

    44 /* 可以在这里提取特定格式的文件路径 */

    45 }//else

    46 } //for

    47 }

    48 return res;

    49 }

    scan_files函数用来扫描指定路径下的文件。比如我们设计一个mp3播放器,我们需要提取mp3格式文件,诸如*.txt*.c文件我们统统不可要的,这时我们就必须扫描路径下所有文件并把*.mp3*.MP3格式文件提取出来。这里我们提取特定格式文件,而是把所有文件名称都通过串口打印出来。

    我们在ffconf.h文件中定义了长文件名称支持(_USE_LFN=2),一般有用到简体中文文件名称的都要长文件名支持。短文件名称是8.3格式,即名称是8个字节,后缀名是3个字节,对于使用英文名称还可以,使用中文名称就很容易长度不够了。使能了长文件名支持后,使用之前需要指定文件名的存储区还有存储区的大小。

    接下来就是使用f_opendir函数打开指定的路径。如果路径存在就使用f_readdir函数读取路径下内容,f_readdir函数可以读取路径下的文件或者文件夹,并保存信息到文件信息对象变量内。f_readdir函数有两个形参,第一个参数为指向路径对象变量的指针,第二个参数为指向文件信息对象的指针。f_readdir函数另外一个特性是自动读取下一个文件对象,即循序运行该函数可以读取该路径下的所有文件。所以,在程序中,我们使用for循环让f_readdir函数读取所有文件,并在读取所有文件之后退出循环。

    f_readdir函数成功读取到一个对象时,我们还不清楚它是一个文件还是一个文件夹,此时我们就可以使用文件信息对象变量的文件属性来判断了,如果判断得出是个文件那我们就直接通过串口打印出来就好了。如果是个文件夹,我们就要进入该文件夹扫描,这时就重新调用扫描函数scan_files就可以了,形成一个递归调用结构,只是我们这次用的参数与最开始时候是不同的,现在是使用子文件夹名称。

    主函数

    代码清单 2514 主函数

    1 int main(void)

    2 {

    3 /* 初始化调试串口,一般为串口1 */

    4 Debug_USART_Config();

    5 printf("******** 这是一个SPI FLASH 文件系统实验 *******\r\n");

    6

    7 //在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化

    8 res_flash = f_mount(&fs,"1:",1);

    9 if (res_flash!=FR_OK) {

    10 printf("!!外部Flash挂载文件系统失败。(%d)\r\n",res_flash);

    11 printf("!!可能原因:SPI Flash初始化不成功。\r\n");

    12 while (1);

    13 } else {

    14 printf("》文件系统挂载成功,可以进行测试\r\n");

    15 }

    16

    17 /* FatFs多项功能测试 */

    18 res_flash = miscellaneous();

    19

    20

    21 printf("\n*************** 文件信息获取测试 **************\r\n");

    22 res_flash = file_check();

    23

    24

    25 printf("***************** 文件扫描测试 ****************\r\n");

    26 strcpy(fpath,"1:");

    27 scan_files(fpath);

    28

    29

    30 /* 不再使用文件系统,取消挂载文件系统 */

    31 f_mount(NULL,"1:",1);

    32

    33 /* 操作完成,停机 */

    34 while (1) {

    35 }

    36 }

    串口在程序调试中经常使用,可以把变量值直观打印到串口调试助手,这个信息非常重要,同样在使用之前需要调用Debug_USART_Config函数完成调试串口初始化。

    使用FatFs进行文件操作之前都使用f_mount函数挂载物理设备,这里我们使用SPI Flash芯片上的FAT文件系统。

    接下来我们直接调用miscellaneous函数进行FatFs设备信息获取、文件定位和格式化写入功能以及目录创建和重命名功能测试。调用file_check函数进行文件信息获取测试。

    scan_files函数用来扫描路径下的所有文件,fpath是我们定义的一个包含100个元素的字符型数组,并将其赋值为SPI Flash芯片物理编号对于的根目录。这样允许scan_files函数见打印SPI Flash芯片内FatFs所有文件到串口调试助手。注意,这里的定义fpaht数组是必不可少的,因为scan_files函数本身是个递归函数,要求实际参数有较大空间的缓存区。

    25.4.3 下载验证

    保证开发板相关硬件连接正确,用USB线连接开发板"USB TO UART"接口跟电脑,在电脑端打开串口调试助手,把编译好的程序下载到开发板。程序开始运行,在串口调试助手可看到每个阶段测试相关信息情况。

     

    转载于:https://www.cnblogs.com/leo0621/p/9980885.html

    展开全文
  • Linux学习系列五:Nand Flash文件系统制作.pdf
  • FatFs 支持 FAT12、FAT16、FAT32 等格式,所以我们利用前面写好的 SPI Flash 芯片驱动,把 FatFs 文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对 SPI Flash 芯片以“文件”格式进行读写操
  • 刷机包内提取 大概百分之六十的刷机包都是有提供内核的(无论卡刷包或者线刷包)。 比如,你下载的卡刷包是:MK90.0-oscar-210227-RELEASE 这是一个mokee的包,我们解压后: 解压后,就是刷机包里的具体内容。其中...
  • 提取Flash芯片信息

    2020-06-18 23:08:23
    目录 0X01 前言 0X02 芯片介绍 ROM芯片 Flash芯片  一、IICEEPROM  二、SPINorFlash  三、ParallelNorFalsh(CFIFlash) ...0X03Flash芯片提取固件 一、物理环境 二、工具环境 三...
  • 第25章 串行FLASH文件系统FatFs 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/firege     本章参考资料:《00index_e.html》,这是...
  • MCU Flash中的只读文件系统

    千次阅读 2012-11-27 19:41:52
    ROM文件系统 ROM文件系统(ROMFS)是一个由eLua构建的微型的,只读的文件系统,它与C库集成在一起,所以你可以使用标准的POSIX(fopen/fclose/fwrite...)来操作它。它也可以直接通过Lua的io模块来控制。这个文件...
  • 功能就是拉入flash生成的EXE,或者包含flash的EXE文件。然后列表框会显示出里面包含的flash 然后根据需要提取吧。 本来应该不需要提取的,一般反编译flash的软件都自带这个功能,但最近自己机器的 JPEXS Free Flash ...
  • 固件提取-flash芯片

    2020-12-07 21:19:38
    fs文件系统,应用程序 固件在无线路由器出厂的时候通常封装在一个flash集成电路里, 现在的flash集成电路通常采用8脚封装,也有更多针脚的, 它焊接在路由器的主板上,相当于台式电脑的硬盘。 路由器FLASH flash...
  • OpenWrt提取系统镜像

    千次阅读 2018-12-25 15:17:34
    前言:在路由器中flash了openwrt系统,并安装了许多软件及相关配置,现想将整个系统的镜像提取出来,并flash到另一台相同的设备中。 方法 1.先查看fireware分区所对应的mtd设备 ## 输入命令 cat /proc/mtd ## 输出...
  • UBI文件系统

    2012-09-04 13:38:14
    在linux-2.6.27以前,谈到Flash文件系统,大家很多时候多会想到cramfs、jffs2、yaffs2等文件系统。它们也都是基于文件系统+mtd+flash设备的架构。linux-2.6.27后,内核加入了一种新型的flash文件系统UBI(Unsorted ...
  • UBI 文件系统

    千次阅读 2012-01-16 18:35:35
    在linux-2.6.27以前,谈到Flash文件系统,大家很多时候多会想到cramfs、jffs2、yaffs2等文件系统。它们也都是基于文件系统+mtd+flash设备的架构。linux-2.6.27后,内核加入了一种新型的flash文件系统UBI(Unsorted ...
  • File Juicer for mac是一款适用于Mac操作系统文件提取工具,可以找到并无损提取MOV,MP4,Flash,ZIP,HTML等格式的图片、文件,帮助用户快速的从文件包中提取需要的内容,免去了查找和筛选内容的复杂步骤。...
  • USB2.0挂载FatFs文件系统

    千次阅读 2017-09-04 17:57:31
    文件系统fatfs、USB协议栈、物理层flash存储芯片关系。 1、单独使用flash芯片,开发flash芯片的响应的驱动即可进行按照 绝对地址 的读写方式进行数据的存储和读取。一般的单片机只做存储的可以这么做。 void W25
  • 桃源文件系统v3.3

    2014-08-04 12:12:37
    4、文件预览或编辑:支持各种图片文件即时预览、媒体或flash文件在线播放、文本及PDF文件在线打开,可在后台扩充文件支持格式。可在线编辑TXT、word、excel、PowerPoint等格式文件,编辑后回存服务器,无需下载修改...
  • real6410移植linux2.6.39.4内核(2)-添加nand flash驱动及启动cramfs文件系统 又经过了一天的奋战,搜索了无数资料,今天终于VFS: Mounted root (cramfs filesystem) 了 ,这句话的意思就是说内核已近发现了...
  • UBI文件系统制作

    万次阅读 2012-07-19 22:16:27
    在linux-2.6.27以前,谈到Flash文件系统,大家很多时候多会想到cramfs、jffs2、yaffs2等文件系统。它们也都是基于文件系统+mtd+flash设备的架构。linux-2.6.27后,内核加入了一种新型的flash文件系统UBI(Unsorted ...
  • ramdisk文件系统的介绍与制作

    万次阅读 2016-09-23 20:21:57
     文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)分区上的文件的存储方法和数据结构,即在存储设备上组织文件的方法。  看了这个概念如果有些懵,下面用两个例子来说明。  a...
  • Linux根文件系统(rootfs原理详解)

    千次阅读 2020-09-23 16:22:31
    linux中有一个让很多初学者都不是特别清楚的概念,叫做“根文件系统”。我接触linux前前后后...文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构;
  • zImage文件提取及逆向分析

    千次阅读 2015-04-18 16:52:50
    技术背景: Android手机获得Root权限,可以让/system和/data分区...众所周知,市面上绝大部分的Android手机文件系统有三个分区,分别是/,/system,/data.根分区(/)是打包为ramdisk.img后,再与kernel的zImage打包为boot.img
  • 在linux-2.6.27以前,谈到Flash文件系统,大家很多时候多会想到cramfs、jffs2、yaffs2等文件系统。它们也都是基于文件系统+mtd+flash设备的架构。linux-2.6.27后,内核加入了一种新型的flash文件系统UBI(Unsorted ...
  • 多种嵌入式文件系统移植集合

    万次阅读 2013-06-01 21:23:34
    1. 嵌入式存储系统 1.1. 计算机组成原理 从冯.诺依曼的存储程序工作原理及计算机的组成来说,计算机由运算器、控制器、存储器和输入/输出设备五大部件组成。其中运算器和控制器统称为中央处理器(CPU),而存储系统...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 16,107
精华内容 6,442
关键字:

flash文件系统提取