FatFs文件系统模块的STM32移植操作(基于STM32的SD卡官方例程说明)

由于想要读取 MP3 文件,故学习一下 FatFs 文件系统。

文章介绍了 FatFs 的移植,对移植代码进行了分析。

SD卡接口函数还需参照:

STM32关于SDIO的控制,控制SD卡_喜暖知寒的博客-CSDN博客

STM32对SD卡的读、写、擦除操作(SDIO模式)(DMA)_喜暖知寒的博客-CSDN博客

只介绍了 SD 卡的 I/O 接口,并且SD卡的操作代码完全为ST库中提供的示例代码。

🚩  毕竟初学,如有错误还望指正。


目录

绪言

FatFs简单了解

简单介绍

官方提供文件目录

程序移植问题

文件系统的结构

底层设备驱动函数

硬件接口函数配置简介(正点原子)

disk_initialize(设备初始化)

disk_status(设备状态获取)

disk_read(扇区读取)

disk_write(扇区写入)

disk_ioctl(其他控制)

get_fattime(获取当前时间)

接口实现代码

0️⃣ 物理编号宏定义

1️⃣ disk_status(设备状态获取)

2️⃣ disk_initialize(设备初始化)

3️⃣ disk_read(扇区读取)

4️⃣ disk_write(扇区写入)

5️⃣ disk_ioctl(其他控制)

6️⃣ get_fattime(获取当前时间)

总结


绪言

本文撰写时,FatFs 官网提供的最新版本为 FatFs R0.14b 。下载此文件时,还会附带下载FatFs的数据手册网页。

FatFs 官网还提供了应用例程,其中包含 STM32 的例程。但是,提供的 STM32 例程是基于STM32F100的。

主要是分析关于 SD 卡的文件系统。参考:

  • [野火]《STM32库开发实战指南》
  • [正点原子]《STM32F1开发指南》

野火和正点原子用的都是 R0.11 版本的,肯定是有些差异的。


FatFs简单了解

简单介绍

FatFs 是用于小型嵌入式系统的通用 FAT/exFAT 文件系统模块。免费且开源。 FatFs 支持FAT12、FAT16、FAT32 等格式。按照 C89 标准编写,与磁盘I/O层完全分离。可以很容易的移植。并且,FatFs还支持 RTOS 。

FatFs 层次结构图

只需会调用 FatFs 提供的接口程序,即可像电脑上编写 C 、Python 等操作文件一样简单了。

使用 FatFs 需要的操作其实就是将底层接口与读写接口连接起来。

官方提供文件目录

主目录下有两个文件夹,为documents、source。

documents主要包含提供的帮助文档
source文件系统代码

其中,source文件夹下的文件为系统代码:

名称描述
00history.txt版本更新历史
00readme.txt介绍source文件下文件功能
ff.cFatFs 模块
ff.hFatFs 和应用模块公用的包含文件
diskio.hFatFs 和磁 盘输入/输出模块共用的包含文件
diskio.c将现有磁盘输入/输出模块连接到FATF的粘合功能示例
ffconf.hFatFs 模块的配置文件
ffunicode.c可选的Unicode实用程序函数
ffsystem.c可选O/S相关函数的示例

对于里面的实现方式,目前不想学习,只要求能够移植,写入硬件接口和使用API就足够了。


程序移植问题

FatFs 既不关心使用何种存储设备,也不关心如何实现。唯一的要求是:它是一个以固定大小的块读取/写入的块设备,可以通过定义的磁盘I/O功能访问。

文件系统的结构

FatFs程序结构图

这是 FatFs 官方所提供的程序之间的依赖网络。其中虚线框不是必须的,为用户自行编写定义。

在官方提供的文件中,ff.cff.hdiskio.h 是不需要操作的

需要修改的有:

  • ffconf.h:通过修改其中的宏定义可裁剪 FatFs 的部分功能。
  • diskio.c:底层驱动函数

FatFs 添加到工程文件就不说了,毕竟和创建工程的操作相差不大。

其中,单一硬件存储器和多硬件存储器在软件系统上有些许不同。

要附加具有不同接口的磁盘驱动程序,需要一些粘合功能来实现 FatFs 和驱动程序之间的接口。

底层设备驱动函数

FatFs 移植需要用户支持的函数:

翻译(自己翻译的,有可能有误):

功能条件(定义在ffconf.h)备注
disk_status
disk_initialize
disk_read
总是底层设备驱动函数
disk_write
get_fattime
disk_ioctl(CTRL_SYNC)
FF_FS_READONLY == 0
disk_ioctl (GET_SECTOR_COUNT)
disk_ioctl (GET_BLOCK_SIZE)
FF_USE_MKFS == 1
disk_ioctl (GET_SECTOR_SIZE)FF_MAX_SS ! =  FF_MIN_SS
disk_ioctl (CTRL_TRIM)FF_USE_TRIM == 1
ff_uni2oem
ff_oem2uni
ff_wtoupper
FF_USE_LIN ! = 0unicode支持函数。将可选模块 ffunicode.c 添加到项目中。支持中文在此设置
ff_cre_syncobj
ff_del_syncobj
ff_req_grant
ff_rel_grant
FF_FS_REENTRANT == 1操作系统相关函数。示例代码在 ffsystem.c 中可用。
ff_mem_alloc
ff_mem_free

FF_USE_LFN == 3

 其中,关于条件的解释

条件含义
条件解释
FF_FS_READONLY用来配置是否为只读操作
FF_USE_MKFS设置是否使能初始化
FF_MAX_SS\FF_MIN_SS设置缓冲区最大值\最小值
FF_USE_TRIM定义是否支持 ATA-TRIM
FF_USE_LFN是否支持长文件名(0\1\2\3可选参数)
FF_FS_REENTRANT切换 FatFs 模块本身的重入

一般情况下,需要配置的函数就6个:

函数功能
disk_status设备状态获取
disk_initialize设备初始化
disk_read扇区读取
disk_write扇区写入
get_fattime获取当前时间
disk_ioctl(CTRL_SYNC)控制设备的其他杂项

硬件接口函数配置简介(正点原子)

正点原子的《STM32开发指南》提供了详细的函数解释!很棒!就是不知道 FatFs 的版本问题是否兼容。

disk_initialize(设备初始化)

disk_status(设备状态获取)

disk_read(扇区读取)

disk_write(扇区写入)

disk_ioctl(其他控制)

get_fattime(获取当前时间)

✅  很简单吧,只要配置了底层硬件接口,其他直接调用 FatFs 提供的 API 函数就可以了。


接口实现代码

❗  不要忘记定义各种条件!!!磁盘的个数不要忘了配置,最小最大存储空间,格式化等等

因为前面刚刚分析STM32提供的操作SD卡的例程,故此用SD卡为例。

代码基于 FatFs R0.14b 的 diskio.c 文件进行更改,更改处会进行注释。

0️⃣ 物理编号宏定义

FatFs 支持多物理设备,要为每个物理设备定义不同的编号。

/* Definitions of physical drive number for each drive */
//此除可自行更改
#define DEV_RAM		0	/* Example: Map Ramdisk to physical drive 0 */
#define DEV_MMC		1	/* Example: Map MMC/SD card to physical drive 1 */
#define DEV_USB		2	/* Example: Map USB MSD to physical drive 2 */

1️⃣ disk_status(设备状态获取)

其中,在diskio.h中定义了设备状态标志,具体如下

/* Disk Status Bits (DSTATUS) */

#define STA_NOINIT		0x01	/* Drive not initialized */
#define STA_NODISK		0x02	/* No medium in the drive */
#define STA_PROTECT		0x04	/* Write protected */
  • STA_NOINIT:设备未初始化
  • STA_NODISK:驱动器中无介质
  • STA_PROTECT:介质受到写保护

定义了枚举

/* Results of Disk Functions */
/* 磁盘功能结果 */
typedef enum {
	RES_OK = 0,		/* 0: Successful */
	RES_ERROR,		/* 1: R/W Error */
	RES_WRPRT,		/* 2: Write Protected */
	RES_NOTRDY,		/* 3: Not Ready */
	RES_PARERR		/* 4: Invalid Parameter */
} DRESULT;

还有关于数据类型的定义,在 ff.h 中定义。

/* 50 */typedef unsigned char	BYTE;	/* char must be 8-bit */

 在 diskio.h 中对 BYTE 进行创建符号

/* 12 *//* Status of Disk Functions */
        /* 磁盘功能状态 */
/* 13 */typedef BYTE	DSTATUS;

 其实要求不高的化,默认状态OK就行了。

/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/

// 传入的数据为物理编号,返回类型为DSTATUS。
DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
	int result;
    
	switch (pdrv) {
	case DEV_RAM :
		result = RAM_disk_status();

		// translate the reslut code here

		return stat;

	case DEV_MMC :
/********************新增上边界***********************/
        return 0;    //默认准备好了
/********************新增下边界***********************/

/********************删除上边界***********************/
		result = MMC_disk_status();

		// translate the reslut code here

		return stat;
/********************删除上边界***********************/
	case DEV_USB :
		result = USB_disk_status();

		// translate the reslut code here

		return stat;
	}
	return STA_NOINIT;
}

要求必须读SD卡状态,可用STM32提供的函数

但是对于 SD_SendSDStatus(uint32_t *psdstatus) 返回值的判断需慎重!

SD_Error SD_SendSDStatus(uint32_t *psdstatus)    //指针为 SD状态寄存器

/********************新增上边界***********************/
    result = SD_Error SD_SendSDStatus(uint32_t *psdstatus)
    if(result == SD_OK)
        return 0;    
    else
        return STA_NOINIT;   
/********************新增下边界***********************/

2️⃣ disk_initialize(设备初始化)

/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
	int result;

	switch (pdrv) {
	case DEV_RAM :
		result = RAM_disk_initialize();

		// translate the reslut code here

		return stat;

	case DEV_MMC :
/********************新增上边界***********************/
        if(SD_Init() == SD_OK)    //SD_Init()是SD初始化函数,无错误返回SD_OK
            return 0;
        else
            return STA_NOINIT;
/********************新增下边界***********************/

/********************删除下边界***********************/
		result = MMC_disk_initialize();

		// translate the reslut code here

		return stat;
/********************删除下边界***********************/

	case DEV_USB :
		result = USB_disk_initialize();

		// translate the reslut code here

		return stat;
	}
	return STA_NOINIT;
}

3️⃣ disk_read(扇区读取)

读取和写就稍微有些复杂

四字节对齐操作

因为分配了数组占用了空间,可以把栈大小改大一点

// startup_stm32f10x_hd.s
Stack_Size      EQU     0x00000400
//地址对齐需要引入头文件
#include <string.h>

/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/
// 读扇区
// @ pdrv 磁盘编号
// @ buff 数据缓冲区地址
// @ sector 扇区地址
// @ count 扇区数
DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	LBA_t sector,	/* Start sector in LBA */
	UINT count		/* Number of sectors to read */
)
{
	DRESULT res;
	int result;

	switch (pdrv) {
	case DEV_RAM :
		// translate the arguments here

		result = RAM_disk_read(buff, sector, count);

		// translate the reslut code here

		return res;

	case DEV_MMC :
/********************新增上边界***********************/
        SD_Error sd_state;  //存储SD卡状态
        //地址问题,假设传入 51 :110011 & 0011 则为 000011,故地址没对齐
        if((DWORD)buff&3)   //因为用到了DMA要求存储区为4字节对齐  buff & 0011
        {
            res = RES_OK;

            while(count--)
            {
                __align(4) DWORD tempbuff[SDCardInfo.CardBlockSize/4];    //__align(4)是mdk语法,四字节对齐
           /*   //align不能分配变量大小空间,下面正确使用方法
            *   __align(4) unit8_t tempbuff[512];
            *   //但是如果分配四字节变量,mdk会自动对齐,所以下面说法也是对的
            *   DWORD tempbuff[SDCardInfo.CardBlockSize/4];
            */
                res = disk_read(DEV_MMC,(BYTE *)tempbuff,sector,1);    //自己调用自己
                if(res != RES_OK) break;
                memcpy(buff,tempbuff,SDCardInfo.CardBlockSize);    //引用头文件为了用它

                buff += SDCardInfo.CardBlockSize;
            }
            return res;
        }
        //这里没判断else,是否还会继续执行??
        
        /* 读取的函数 */
        sd_state = SD_ReadMultiBlocks(buff, sector * SDCardInfo.CardBlockSize, 
                           SDCardInfo.CardBlockSize, count)    //这里读的是SDIO结构体里对块大小的定义
        if(sd_state == SD_OK)
        {
            /* Check if the Transfer is finished */
            sd_state = SD_WaitReadOperation();
            while(SD_GetStatus() != SD_TRANSFER_OK);
        }
        if(sd_state == SD_OK)
            res = RES_OK;
        else
            res = RES_ERROR;

/********************新增下边界***********************/


/********************删除上边界***********************/
		// translate the arguments here

		result = MMC_disk_read(buff, sector, count);

		// translate the reslut code here

		return res;

/********************删除下边界***********************/

	case DEV_USB :
		// translate the arguments here

		result = USB_disk_read(buff, sector, count);

		// translate the reslut code here

		return res;
	}

	return RES_PARERR;    //参数无效
}

4️⃣ disk_write(扇区写入)

/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if FF_FS_READONLY == 0

DRESULT disk_write (
	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	LBA_t sector,		/* Start sector in LBA */
	UINT count			/* Number of sectors to write */
)
{
	DRESULT res;
	int result;

	switch (pdrv) {
	case DEV_RAM :
		// translate the arguments here

		result = RAM_disk_write(buff, sector, count);

		// translate the reslut code here

		return res;

	case DEV_MMC :
/********************新增上边界***********************/
        SD_Error sd_state;  //存储SD卡状态
        if((DWORD)buff&3)   //因为用到了DMA要求存储区为4字节对齐  buff & 0011
        {
            res = RES_OK;

            while(count--)
            {
                __align(4) DWORD tempbuff[SDCardInfo.CardBlockSize/4];    //__align(4)是mdk语法,四字节对齐
                
                memcpy(tempbuff,buff,SDCardInfo.CardBlockSize);    //引用头文件为了用它
                res = disk_read(DEV_MMC,(BYTE *)tempbuff,sector,1);    //自己调用自己
                if(res != RES_OK) break;
               
                buff += SDCardInfo.CardBlockSize;
            }
            return res;
        }
        //这里没判断else,是否还会继续执行??
        
        /* 读取的函数 */
        sd_state = SD_WriteMultiBlocks((uint8_t)buff, sector * SDCardInfo.CardBlockSize, 
                           SDCardInfo.CardBlockSize, count)    //这里读的是SDIO结构体里对块大小的定义
        if(sd_state == SD_OK)
        {
            /* Check if the Transfer is finished */
            sd_state = SD_WaitWriteOperation();
            while(SD_GetStatus() != SD_TRANSFER_OK);
        }
        if(sd_state == SD_OK)
            res = RES_OK;
        else
            res = RES_ERROR;

/********************新下上边界***********************/
		// translate the arguments here

		result = MMC_disk_write(buff, sector, count);

		// translate the reslut code here

		return res;

	case DEV_USB :
		// translate the arguments here

		result = USB_disk_write(buff, sector, count);

		// translate the reslut code here

		return res;
	}

	return RES_PARERR;
}

#endif

5️⃣ disk_ioctl(其他控制)

command 命令在 diskio.h 文件中有定义!

/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	DRESULT res;
	int result;

	switch (pdrv) {
	case DEV_RAM :

		// Process of the command for the RAM drive

		return res;

	case DEV_MMC :
/********************新增上边界***********************/
        switch (cmd)
        {
            case GET_SECTOR_SIZE: //扇区大小
                *(WORD *)buff = SDCardInfo.CardBlockSize;
                break;
            case GET_BLOCK_SIZE: //擦除扇区大小
                *(DWORD *)buff = 1;
                break;                
            case GET_SECTOR_COUNT: //扇区数量
                *(DWORD *)buff = SDCardInfo.CardCapacity/SDCardInfo.CardBlockSize;
                break;
            case CTRL_SYNC:
                break;
        }
        
/********************新增下边界***********************/

/********************删除上边界***********************/
		// Process of the command for the MMC/SD card

		return res;
/********************删除下边界***********************/
	case DEV_USB :

		// Process of the command the USB drive

		return res;
	}

	return RES_PARERR;
}

6️⃣ get_fattime(获取当前时间)

有关时间问题,FatFs 写了一个时间的函数

/* Timestamp */
#if FF_FS_NORTC == 1
#if FF_NORTC_YEAR < 1980 || FF_NORTC_YEAR > 2107 || FF_NORTC_MON < 1 || FF_NORTC_MON > 12 || FF_NORTC_MDAY < 1 || FF_NORTC_MDAY > 31
#error Invalid FF_FS_NORTC settings
#endif
#define GET_FATTIME()	((DWORD)(FF_NORTC_YEAR - 1980) << 25 | (DWORD)FF_NORTC_MON << 21 | (DWORD)FF_NORTC_MDAY << 16)
#else
#define GET_FATTIME()	get_fattime()
#endif

自然也是可以写 get_fattime() 给覆盖掉的,由 FF_FS_NORTC 所决定。

例如野火所定义的时间戳函数

__weak DWORD get_fattime(void) {
	/* 返回当前时间戳 */
	return	  ((DWORD)(2015 - 1980) << 25)	/* Year 2015 */
			| ((DWORD)1 << 21)				/* Month 1 */
			| ((DWORD)1 << 16)				/* Mday 1 */
			| ((DWORD)0 << 11)				/* Hour 0 */
			| ((DWORD)0 << 5)				  /* Min 0 */
			| ((DWORD)0 >> 1);				/* Sec 0 */
}

总结

今天为了移植系统第一次看野火的视频教程,觉得和正点原子有很多不同

  • 想快速使用:用正点原子
  • 想仔细学习:用野火

还没有接触到 FatFs 在 FreeRTOS 上的移植,不知道会不会遇到什么困难。


✅  OK,到这里就移植完了!剩下的用户层接口就都是一样的。其实移植更改不多。修改底层接口,修改宏定义以增加、减少、更改功能就够了!

📚  嗯,就到这里吧 ~ 明天再看API相关的。晚安!

  • 5
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值