STM32移植FatFs文件系统详细步骤说明

STM32通过SDIO驱动并移植FatFs文件系统详细步骤说明

对于sd卡sdio卡tf卡和mmc卡的介绍就不说了,对于sdio的介绍和fatfs的介绍不做说明,默认读者已经掌握这些基础知识
本说明采用STM32F407ZGT6单片机,最新版本的Fatfs的文件,对于之前的Fatfs文件系统也会介绍

一. 架构说明

在这里插入图片描述
最底层是各类存储设备,FATFS支持SD卡,TF卡以及FLASH和各类存储器,SDIO是和存储设备直接进行交互的接口,FATFS是建立在SDIO接口之上的管理协议,最上层的应用层是根据我们自己的需求去编写的。

SDIO说明

在这里插入图片描述

TF卡与SDIO接口硬件连接

在这里插入图片描述
TF卡与SDIO硬件的连接只需要连接6根线即可,将TF接在STM32的SDIO硬件接口上。

SDIO移植说明

关于SDIO的初始化和各类驱动函数都是为了给FATFS文件系统做基础,因为FATFS文件系统的接口需要初始化,读TF卡信息以及读指定块数据和写指定块数据函数,并且需要有返回值,入参出参的格式也都是固定的,所以要和FATFS文件系统格式保持一致

1. 初始化函数

建立一个空白的STM32工程,然后将SDIO的库函数添加进来,然后首先初始化和使能SDIO的时钟,设置时钟沿,设置旁路时钟,设置节能模式,设置数据宽度为1位,设置硬件流控制,最后设置时钟分频,这是STM32种SDIO的配置结构体。使能GPIO的时钟之后我们配置所有的GPIO的输出引脚,全部设置为复用推挽输出,到此整个SDIO初始化就完成了,但是此初始化需要返回一个变量,用于判断是否初始化成功的参数,这样做的目的是为后面移植FATFS文件系统做准备。

/*
Name:uint8_t Sd_init(void)
----------------------
Des:  SDIO初始化函数
Ref:
Paras: 
Author: zx
Date:   2023年11月09日
*/
uint8_t SdInit(void)
{

	GPIO_InitTypeDef gpio_init_struct = {0};

	/*初始化GPIO*/
    __HAL_RCC_SDIO_CLK_ENABLE();
    SD_D0_GPIO_CLK_ENABLE();
    SD_D1_GPIO_CLK_ENABLE();
    SD_D2_GPIO_CLK_ENABLE();
    SD_D3_GPIO_CLK_ENABLE();
    SD_SCK_GPIO_CLK_ENABLE();
    SD_CMD_GPIO_CLK_ENABLE();
    
    /* 配置D0引脚 */
    gpio_init_struct.Pin = SD_D0_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;
    gpio_init_struct.Pull = GPIO_PULLUP;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio_init_struct.Alternate = SD_D0_GPIO_AF;
    HAL_GPIO_Init(SD_D0_GPIO_PORT, &gpio_init_struct);
    
    /* 配置D1引脚 */
    gpio_init_struct.Pin = SD_D1_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;
    gpio_init_struct.Pull = GPIO_PULLUP;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio_init_struct.Alternate = SD_D1_GPIO_AF;
    HAL_GPIO_Init(SD_D1_GPIO_PORT, &gpio_init_struct);
    
    /* 配置D2引脚 */
    gpio_init_struct.Pin = SD_D2_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;
    gpio_init_struct.Pull = GPIO_PULLUP;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio_init_struct.Alternate = SD_D2_GPIO_AF;
    HAL_GPIO_Init(SD_D2_GPIO_PORT, &gpio_init_struct);
    
    /* 配置D3引脚 */
    gpio_init_struct.Pin = SD_D3_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;
    gpio_init_struct.Pull = GPIO_PULLUP;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio_init_struct.Alternate = SD_D3_GPIO_AF;
    HAL_GPIO_Init(SD_D3_GPIO_PORT, &gpio_init_struct);
    
    /* 配置SCK引脚 */
    gpio_init_struct.Pin = SD_SCK_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;
    gpio_init_struct.Pull = GPIO_PULLUP;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio_init_struct.Alternate = SD_SCK_GPIO_AF;
    HAL_GPIO_Init(SD_SCK_GPIO_PORT, &gpio_init_struct);
    
    /* 配置CMD引脚 */
    gpio_init_struct.Pin = SD_CMD_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;
    gpio_init_struct.Pull = GPIO_PULLUP;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio_init_struct.Alternate = SD_CMD_GPIO_AF;
    HAL_GPIO_Init(SD_CMD_GPIO_PORT, &gpio_init_struct);

    /* 配置SD */
    sd_handle.Instance = SDIO;
    sd_handle.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
    sd_handle.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
    sd_handle.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
    sd_handle.Init.BusWide = SDIO_BUS_WIDE_1B;
    sd_handle.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
    sd_handle.Init.ClockDiv = 1;
    if (HAL_SD_Init(&sd_handle) != HAL_OK)
    {
        return 1;
    }   
    /* 获取SD信息 */
    HAL_SD_GetCardInfo(&sd_handle, &sd_card_info);
    
    /* 配置4bit总线宽度 */
    if (HAL_SD_ConfigWideBusOperation(&sd_handle, SDIO_BUS_WIDE_4B) != HAL_OK)
    {
        return 2;
    }  
    return 0;
}

注意,一开始配置的是单线数据传输,在后面我们将单线数据传输改为了四线数据传输,这样可提高速度,至于为啥不一开始就配置成四线的,这个问题我也不太清楚

2. 读TF卡信息函数

在这个函数内,我们直接调用HAL库的函数即可获取TF卡的容量大小,剩余空间以及卡类型的信息,

/*
Name:uint8_t SdGetCardInfo
----------------------
Des:  读取SD卡信息
Ref:
Paras: info:SD卡信息
Author: zx
Date:   2023年11月09日
*/
uint8_t SdGetCardInfo(HAL_SD_CardInfoTypeDef *info)
{
    if (HAL_SD_GetCardInfo(&sd_handle, info) != HAL_OK)
    {
        return 1;
    }   
    return 0;
}

3. 读SD卡指定数量的块数据

此函数可以读取TF卡种指定块的数据,也是用于给文件系统做基础。

/*
Name:uint8_t SdGetCardInfo
----------------------
Des:  读SD卡指定数量的块数据
Ref:
Paras: buf: 数据保存的起始地址;addr: 块地址;count: 块数量;
Author: zx
Date:   2023年11月09日
*/
uint8_t SdReadDisk(uint8_t *u8pbuf, uint32_t u32addr, uint32_t u32count)
{
    uint32_t u32timeout = SD_DATATIMEOUT;
    
    if (HAL_SD_ReadBlocks(&sd_handle, u8pbuf, u32addr, u32count, SD_DATATIMEOUT) != HAL_OK)
    {
        return 1;
    }
    
    while ((HAL_SD_GetCardState(&sd_handle) != HAL_SD_CARD_TRANSFER) && (--u32timeout != 0));
   
    if (u32timeout == 0)
    {
        return 1;
    }   
    return 0;
}

4. 写SD卡指定数量的块数据

/*
Name:uint8_t SdWriteDisk
----------------------
Des:  写SD卡指定数量的块数据
Ref:
Paras: buf: 数据保存的起始地址;addr: 块地址;count: 块数量;
Author: zx
Date:   2023年11月09日
*/
uint8_t SdWriteDisk(uint8_t *u8pbuf, uint32_t u32addr, uint32_t u32count)
{
    uint32_t u32timeout = SD_DATATIMEOUT;
    
    if (HAL_SD_WriteBlocks(&sd_handle, u8pbuf, u32addr, u32count, SD_DATATIMEOUT) != HAL_OK)
    {
        return 1;
    }
    
    while ((HAL_SD_GetCardState(&sd_handle) != HAL_SD_CARD_TRANSFER) && (--u32timeout != 0));
    
    if (u32timeout == 0)
    {
        return 1;
    }
    
    return 0;
}

到此,FATFS文件系统需要的函数已经全部编写完成,能保证SDIO和TF正常通讯的基础上可跳过下面两个函数,下面两个函数是我写的专门用来测试SDIO与TF卡通讯是否正常的函数,是修改正点原子的程序。在FATFS文件系统中用不到下面的函数,仅仅测试使用

5. 打印TF卡信息函数,可在初始化之后调用

打印TF的信息,可用来检查TF卡和SDIO连接正常与否,若正常,则可以打印TF的信息。若打印都失败,就要检查硬件和TF卡是否格式化为FAT32格式,必须保证这一步没有任何问题方能进行下一步。

/*
Name:void ShowSdInfo(void)
----------------------
Des:  将SD卡信息通过串口打印出来
Ref:
Paras: 
Author: zx
Date:   2023年11月10日
*/
void ShowSdInfo(void)
{
    HAL_SD_CardCIDTypeDef sd_card_cid = {0};
    
    HAL_SD_GetCardCID(&sd_handle, &sd_card_cid);
    
    USART1_printf("Card Type: %s\r\n", (sd_card_info.CardType == CARD_SDSC) ? ((sd_card_info.CardVersion == CARD_V1_X) ? ("SDSC V1") :
                                                                         ((sd_card_info.CardVersion == CARD_V1_X) ? ("SDSC V2") :
                                                                         (""))) :
                                ((sd_card_info.CardType == CARD_SDHC_SDXC) ? ("SDHC") :
                                ((sd_card_info.CardType == CARD_SECURED) ? ("SECURE") :
                                (""))));
    USART1_printf("Card ManufacturerID:%d\r\n", sd_card_cid.ManufacturerID);
    USART1_printf("Card RCA:%d\r\n", sd_card_info.RelCardAdd);
    USART1_printf("LogBlockNbr:%d \r\n", sd_card_info.LogBlockNbr);
    USART1_printf("LogBlockSize:%d \r\n", sd_card_info.LogBlockSize);
    USART1_printf("Card Capacity:%d MB\r\n", (uint32_t)(((uint64_t)sd_card_info.LogBlockNbr * sd_card_info.LogBlockSize) >> 20));
    USART1_printf("Card BlockSize:%d\r\n\r\n", sd_card_info.BlockSize);
}

6. 读取SD卡第0扇区的数据

/*
Name:void SdReadTest(void)
----------------------
Des:  读取SD卡第0扇区的数据
Ref:
Paras: 
Author: zx
Date:   2023年11月10日
*/
void SdReadTest(void)
{
    uint8_t *buf;
    uint16_t index;
    
    if (buf == NULL)
    {
        return;
    }
    
    /* 读取并打印SD卡第0个块的数据 */
    if (SdReadDisk(buf, 0, 1) == 0)
    {
        USART1_printf("Block 0 Data:\r\n");
        for (index=0; index<sd_card_info.BlockSize; index++)
        {
            USART1_printf("%02X ", buf[index]);
        }
        USART1_printf("\r\nData End\r\n");
    }
    else
    {
        USART1_printf("SD read Failure!\r\n");
    }
}

到此,全部的驱动就已经移植完成了,若初始化SDIO之后可以在串口助手中看到正常显示的TF卡信息和读到的第0扇区的所有数据,那么就成功一半了

最后贴上在c文件中声明的句柄和结构体,这些可以根据自己的嵌入式软件编码规范修改

/* SD句柄 */
SD_HandleTypeDef sd_handle = {0};
/* SD信息 */
HAL_SD_CardInfoTypeDef sd_card_info = {0};

FATFS文件系统移植

版本区别

没啥区别,就是早期的版本有interger.h头文件,是定义数据类型的,现在把这个头文件封装在了ff.c里,现在的版本没有了这个头文件,移植方法和操作都是一样的。
在这里插入图片描述

移植步骤

  1. 把source中的文件除了两个txt说明文件的其他文件都拷到自己创建的工程文件里,在工程中建立一个fatfs的文件夹,放进去,然后就是把头文件都添加进去,把fatfs的路径进行设置。在这里插入图片描述
    将文件都添加进来之后在diskio.c中移植接口,整个FATFS只修改两个文件,一个是diskio.c和ffcon.h,diskio是接口程序,ffcon.h是头文件,和FreeRtos的config文件是一样的,用来配置各种功能和裁剪。言归正传,移植接口。
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module SKELETON for FatFs     (C)ChaN, 2019        */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be        */
/* attached to the FatFs via a glue function rather than modifying it.   */
/* This is an example of glue functions to attach various exsisting      */
/* storage control modules to the FatFs module with a defined API.       */
/*-----------------------------------------------------------------------*/

#include "ff.h"			/* Obtains integer types */
#include "diskio.h"		/* Declarations of disk functions */
#include "app_sd_card.h"

/* 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 */

/************************************************************************/
/*手动新增*/
#define SD_CARD     0       /* SD卡,卷标为0 */
#define EX_FLASH    1       /* 外部spi flash,卷标为1 */
/************************************************************************/
/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/

DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
    return RES_OK;
}
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	switch (pdrv) 
	{
	    case SD_CARD :
	    {
	    	return SdInit();	    
	    }
	}
	return STA_NOINIT;
}



/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

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 */
)
{
	switch (pdrv)
	{
	    case SD_CARD :
	    {	    
	    	return SdReadDisk(buff, sector, count);
	    }		
	}
	return RES_PARERR;
}



/*-----------------------------------------------------------------------*/
/* 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 */
)
{
	switch (pdrv) 
	{
	    case DEV_RAM :
	    {	    
	    	return SdWriteDisk((uint8_t *)buff, sector, count);
	    }
	}
	return RES_PARERR;
}

#endif


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

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

	switch (pdrv) {
	case SD_CARD :

        switch (cmd)
        {
            case CTRL_SYNC:
                result = RES_OK;
                break;

            case GET_SECTOR_SIZE:
                *(DWORD *)buff = 512;
                result = RES_OK;
                break;

            case GET_BLOCK_SIZE:
                *(WORD *)buff = sd_card_info.LogBlockSize;
                result = RES_OK;
                break;

            case GET_SECTOR_COUNT:
                *(DWORD *)buff = sd_card_info.LogBlockNbr;
                result = RES_OK;
                break;

            default:
                result = RES_PARERR;
                break;
        }
		return result;
	}
	return RES_PARERR;
}

将SDIO写的函数添加到diskio.c中的函数中,初始化,读TF卡信息以及读写块的函数添加进来。

/*手动新增*/
#define SD_CARD     0       /* SD卡,卷标为0 */
#define EX_FLASH    1       /* 外部spi flash,卷标为1 */

这两个宏定义是我们为SD卡定义的识别代号,也就是卷标。
然后我门需要定义一个结构体,FATFS *fs[FF_VOLUMES]; 这个结构体是定义一个磁盘所需要的信息,我们读写都会要操作这个结构体,另外需要给磁盘定义一个工作区,保存FATFS文件管理系统的参数,这个工作区是必需的,否则FATFS文件系统会运行不起来,这个结构体也是必需的,因为它相当于一个指针,指向这个磁盘。如果同时挂载两个磁盘,那么就需要定义两个工作区。

/*创建一个SD卡工作区*/
FATFS SDWorkBuf[1];
/*创建一个文件注册区,FATFS是枚举类型*/

/* 逻辑磁盘工作区(在调用任何FATFS相关函数之前,必须先给fs申请内存) */
FATFS *fs[FF_VOLUMES]; 

然后我们还需要定义一个文件结构体一个文件夹结构体,这两个结构体是定义的实体,在操作文件或者文件夹的时候会直接操作这个结构体,这两个结构体指向TF卡中的文件和文件夹。

/*定义一个文件结构体*/
FIL fil;
/*定义一个文件夹结构体*/
DIR dir;

还需要定义一个文件缓冲区,这个缓冲区用于从TF卡中读取数据,将数据暂存在这个缓存中,另外还需要一个u32的变量,用于计数,这个是文件系统API函数要填的参数,以上的定义都是必需的。

/*定义一个u32的变量,用于计数*/
uint32_t bw = 0;
/*定义一个数据缓冲区*/
uint8_t bbuff[200];

移植了所有的文件,定义了所有的参数和结构体之后就可以进行编译,遇到啥问题改啥问题即可,至此,FATFS文件系统的移植就结束了

FATFS文件系统的操作

第一步,首先需要给每个TF卡申请内存区域,为什么说是每个,是因为FATFS文件系统支持10个磁盘,所以写一个通用的函数用于申请内存,这步等于是将一个结构体指针指向给TF卡创建的工作区结构体,我们操作这个指针就是在操作这个TF卡的数据。

/*
Name:uint8_t ApplyForMemory(void)
----------------------
Des:  为SD卡申请内存
Ref:
Paras: 
Author: zx
Date:   2023年11月10日
*/
uint8_t ApplyForMemory(void)
{
    uint8_t u8LoopData = 0;
    uint8_t u8Res = 0;
	
    /*给所有连接得SD卡申请内存区域,FF_VOLUMES是挂载SD卡个数*/
    for (u8LoopData = 0; u8LoopData < FF_VOLUMES; u8LoopData++)
    {
        fs[u8LoopData] = SDWorkBuf;   /* 为磁盘i工作区申请内存 */
        if (!fs[u8LoopData])
		{
			break;
		}
    }   
    if (u8LoopData == FF_VOLUMES && u8Res == 0)
    {
        return 0;   /* 申请有一个失败,即失败. */
    }
    else 
    {
        return 1;
    }
}

第二步,就是需要挂载TF卡,挂载TF卡的意思就是初始化外设以及各个参数,并且将TF卡注册到FATFS文件系统中,挂载成功之后才可以进行各项操作。

	/* 挂载SD卡 */
	res = f_mount(fs[0], "0:", 1);
	if(res == FR_OK)
	{
		USART1_printf("挂载成功\r\n");
	}
	else
	{
		USART1_printf("挂载失败\r\n",res);
	}

当显示TF卡挂载成功之后就可以创建文件夹和创建文件,当然,读写文件夹也是没有问题的,下面简单写几个函数用于打开文件和读文件,首先我们在打开一个草滩小王子的文件夹,然后打开这个文件夹,打开一个名为测试.txt的文档,并且读出里面的数据并打印出来,FA_READ函数是读,如果打开文件夹是要写,参数就改为FA_WRITE,这个参数还有很多选项,大家可查阅FATFS的IAP技术手册。

	res = f_opendir(&dir,"草滩小王子");
	if(res == FR_OK)
	{
		USART1_printf("打开文件成功\r\n",sizeof(SDWorkBuf));
	}
	else
	{
		USART1_printf("打开文件失败\r\n");
	}	
	res = f_open(&fil, "0:/草滩小王子/测试.txt", FA_READ); 
	if(res == FR_OK)
	{
		USART1_printf("打开文件成功\r\n");
	}
	else
	{
		USART1_printf("打开文件失败\r\n");
	}
	f_read(&fil,bbuff,64,&bw);
	f_close(&fil);
	Usart1_SendBuf(bbuff,sizeof(bbuff));

下面是创建一个名为草滩小王子的文件夹例子

	f_chdir("/");
	res = f_mkdir("草滩小王子");
	if(res == FR_OK)
	{
		USART1_printf("创建文件夹成功\r\n");
	}
	else
	{
		USART1_printf("创建文件夹失败:%d\r\n");
	}

下面是打开一个在草滩小王子文件夹下的测试.txt的文件,并且给文件中写入数据的例子

	res = f_open(&fil, "0:/草滩小王子/测试.txt", FA_WRITE	);  
	if(res == FR_OK)
	{
		USART1_printf("打开文件成功\r\n");
	}
	else
	{
		USART1_printf("打开文件失败\r\n");
	}
	/*这个函数忘记啥了,有空了查一查*/
	f_lseek(&fil, f_size(&fil));
	/*给文件中写入数据*/
	f_printf(&fil, "我是小绵羊!\r");
	f_printf(&fil, "我是小绵羊!\r");
	f_printf(&fil, "我是小绵羊!\r");
    f_printf(&fil, "我是小绵羊!\r");
	f_printf(&fil, "我是小绵羊!\r");
	/*此函数用于向文件中写入数据,f_printf也可以实现此功能*/
	f_write(&fil, "我是小绵羊!\r",100,&bw); 
	/*关闭文件*/
	f_close(&fil);

总结:SDIO是专门用于操作TF、SD卡的接口,速度比SPI快,配置方便,并且支持单线数据传输和四线数据传输,在我们的程序中,就用了四线并口传输,速度嘎嘎猛。如果要使用SPI也可以,将diskio.c中将SDIO的驱动更换成SPI的驱动即可,其他都一样。FATFS文件系统操作方便,支持裸奔和操作系统。是管理大容量存储器的比较好的方案,之前还用过南京沁恒微的CH378文件管理芯片,对比FATFS文件管理系统,效率和操作方法明显不如FATFS文件管理系统,所以推荐使用SDIO+FATFS文件系统的方案。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值