3.1、CUBEMX使用FATFS读写SPI_FLASH

所需工具
  • CUBEMX5.6
  • STM32F103ZET6开发板(正点原子战舰)
  • LINK及其他线束
1、CUBEMX设置
  • 设置RCC
    在这里插入图片描述切记选择第三个,否则串口无法使用。
  • 设置串口
    在这里插入图片描述
  • 设置SPI_FLASH
    在这里插入图片描述
  • 设置FATFS
    在这里插入图片描述
  • CODE_PAGE指的是编码格式,可以使用中文编码,只是占用空间会比较大
  • USE_LFN提供了四个选项,BSS,STACK,HEAP,或者禁用。
    该选项主要是控制是否使用中长文件名,如果不是用的话可以禁用掉。
    选用BSS的话则将缓冲放入BSS段内的静态缓冲区,大小在编译时间已经固定,不能扩容。
    STACK和HEAP就不说了,一般选择STACK.
  • 缓冲区内大小为(FF_MAX_LFN + 1)* 2字节,另外在启用exFAT时额外占用(FF_MAX_LFN + 44)/15 * 32个字节数。参考:FatFs Module Application Note
  • 短文件名一般是8.3格式,即文件名最大8个字符,后缀名最多3个字符。 SFN允许的字符是ASCII字母数字,一些ASCII标记( $%’-_ @〜`!(){} ^#& )和非ASCII字符(\ x80-\ xFF)。
  • MAX_SS是最大扇区大小,W25Q128的扇区大小是4KB,所以选择了4096,这里引用别人博客的一段话

为什么最大扇区大小是4096Byte?一般别人都是512Byte? 其实这个是根据你自己使用的存储芯片和驱动相关的。因为我使用的W25Q256这款芯片是最小擦除单位是4096。不使用512byte是因为效率大大降低但是优点是空间利用率会大大提高。比如你文件系统最大分区是512,但是芯片最小擦除单位是4096,那么你在驱动就要实现先用缓存区把整个扇区4096byte全部读出来,然后判读其中写入512byte中有没有擦除过(即全0xFF),没有的话先擦除,在把数据写入缓存区最后写入芯片。所以步骤繁琐效率低,但是优点就是存储空间的利用率会大大提高,避免太多浪费。 我很懒,所以选择使用4096。参考: STM32CbueMX之Fatfs移植到SPI_Flash

2、移植修改文件
  1. 添加W25Q128驱动文件,详情在这
  2. 修改user_diskio.c文件,修改完如下所示
/* USER CODE BEGIN Header */
/**
 ******************************************************************************
  * @file    user_diskio.c
  * @brief   This file includes a diskio driver skeleton to be completed by the user.
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2020 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
 /* USER CODE END Header */

#ifdef USE_OBSOLETE_USER_CODE_SECTION_0
/* 
 * Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0)
 * To be suppressed in the future. 
 * Kept to ensure backward compatibility with previous CubeMx versions when 
 * migrating projects. 
 * User code previously added there should be copied in the new user sections before 
 * the section contents can be deleted.
 */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#endif

/* USER CODE BEGIN DECL */

/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "ff_gen_drv.h"
#include "w25qxx.h" 

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;
#define FLASH_SECTOR_SIZE 	(4096/FLASH_BLOCK_SIZE)
#define  FLASH_SECTOR_COUNT    ((12*1024*1024)/FLASH_SECTOR_SIZE)//12的单位是MB
#define FLASH_BLOCK_SIZE   	    (8)     	    
/*
FLASH_SECTOR_SIZE 	 设置为512或者4096都可以使用,但是在512的时候,文件系统认为FLASH中的扇区大小是512字节,在使用的时候,效率远没有4096高
FLASH_SECTOR_COUNT    挂载的总容量,根据自己需求
FLASH_BLOCK_SIZE   	  每次擦除的块的个数,原子哥写的是8,因为FLASH的最小擦除单位为扇区(4KB),原子哥的FLASH_SECTOR_SIZE 为512,\
所以是512*8,我们用1即可。
*/
/* USER CODE END DECL */

/* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize (BYTE pdrv);
DSTATUS USER_status (BYTE pdrv);
DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
  DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);  
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */

Diskio_drvTypeDef  USER_Driver =
{
  USER_initialize,
  USER_status,
  USER_read, 
#if  _USE_WRITE
  USER_write,
#endif  /* _USE_WRITE == 1 */  
#if  _USE_IOCTL == 1
  USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};
/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Initializes a Drive
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS USER_initialize (
	BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{
  /* USER CODE BEGIN INIT */
    Stat = STA_NOINIT;
    if(W25Q128 == BSP_W25QXX_ReadID())
      {
        /* 设备ID读取结果正确 */
        Stat &= ~STA_NOINIT;
      }
      else
      {
        /* 设备ID读取结果错误 */
        Stat = STA_NOINIT;;
      }
        BSP_W25QXX_Init();
    return Stat;
  /* USER CODE END INIT */
}
 
/**
  * @brief  Gets Disk Status 
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS USER_status (
	BYTE pdrv       /* Physical drive number to identify the drive */
)
{
  /* USER CODE BEGIN STATUS */
    if(pdrv != 0)
        Stat = STA_NOINIT;
    else
        Stat &= ~STA_NOINIT;
    return Stat;
  /* USER CODE END STATUS */
}

/**
  * @brief  Reads Sector(s) 
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to read (1..128)
  * @retval DRESULT: Operation result
  */
DRESULT USER_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 */
)
{
  /* USER CODE BEGIN READ */
    DRESULT res = RES_ERROR;
    if(!count)return RES_PARERR; //count不能等于0,否则返回参数错误
        for(; count > 0; count--)
        {
            BSP_W25QXX_Read(buff, sector * FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE);
            sector++;
            buff += FLASH_SECTOR_SIZE;
                    res = RES_OK;
        }
    //处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
   return res;
  /* USER CODE END READ */
}

/**
  * @brief  Writes Sector(s)  
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to write (1..128)
  * @retval DRESULT: Operation result
  */
#if _USE_WRITE == 1
DRESULT USER_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 */
)
{ 
  /* USER CODE BEGIN WRITE */
        DRESULT res = RES_ERROR;
    if(!count)return res; //count不能等于0,否则返回参数错误
       for(; count > 0; count--)
        {
            BSP_W25QXX_Write((uint8_t*)buff, sector * FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE);
            sector++;
            buff += FLASH_SECTOR_SIZE;
                    res = RES_OK;
        }

    //处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
    return res;
  /* USER CODE HERE */
  /* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */

/**
  * @brief  I/O control operation  
  * @param  pdrv: Physical drive number (0..)
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT USER_ioctl (
	BYTE pdrv,      /* Physical drive nmuber (0..) */
	BYTE cmd,       /* Control code */
	void *buff      /* Buffer to send/receive control data */
)
{
  /* USER CODE BEGIN IOCTL */
    DRESULT res = RES_ERROR;
        switch(cmd)
        {
        case CTRL_SYNC:
            res = RES_OK;
            break;

        case GET_SECTOR_SIZE:
            *(WORD*)buff = FLASH_SECTOR_SIZE;
            res = RES_OK;
            break;

        case GET_BLOCK_SIZE:
            *(WORD*)buff = FLASH_BLOCK_SIZE;
            res = RES_OK;
            break;

        case GET_SECTOR_COUNT:
            *(DWORD*)buff = FLASH_SECTOR_COUNT;
            res = RES_OK;
            break;

        default:
            res = RES_PARERR;
            break;
        }   
    return res;
  /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

FATFS的最小操作单位为簇,在使用flash的时候,可以简单的理解为簇就是扇区,下方的三个宏其实就是FATFS对与总容量和最小操作单位的读取操作。

命令返回值描述
GET_BLOCK_SIZEFLASH_BLOCK_SIZE返回以扇区为单位的存储阵列的擦除块大小赋给 Buffer 指向的 DWORD 变量。当擦除块大小未知或是磁盘设备时,返回 1 。只在 f_mkfs 函数中,使用了该命令。告诉上层每次擦除的时候最小的操作单位是多少,由于FLASH的最小擦除单位为4096,所以 F L A S H   B L O C K   S I Z E = 4096 F L A S H   S E C T O R   S I Z E FLASH_~BLOCK_~SIZE = \dfrac{4096}{FLASH_~SECTOR_~SIZE} FLASH BLOCK SIZE=FLASH SECTOR SIZE4096
GET_SECTOR_SIZEFLASH_SECTOR_SIZE只有当FF_MAX_SS > FF_MIN_SS时才需要这个命令。通俗点来说,告诉FATFS系统每个扇区有多少大容量
GET_SECTOR_COUNTFLASH_SECTOR_COUNT挂载的总容量大小 ,这里划分了W25Q128的前12MB空间, F L A S H   S E C T O R   C O U N T = x ∗ 1024 ∗ 1024 F L A S H   S E C T O R   S I Z E FLASH_~SECTOR_~COUNT = \dfrac{x* 1024*1024}{FLASH_~SECTOR_~SIZE} FLASH SECTOR COUNT=FLASH SECTOR SIZEx10241024
  • 在main.c文件添加如下内容
#include <stdio.h>
#include <string.h>
FATFS spi_fs;
FIL fil;
unsigned int count = 0;
unsigned char work[4096] = {0};
unsigned char read_buf[50] = {0};
unsigned char write_buf[50] = "hello sudaroot\r\n";
void test()
{
    FRESULT retSD;
     printf("\r\n ****** FatFs Example ******\r\n\r\n");
    /*##-1- Register the file system object to the FatFs module ##############*/
    retSD = f_mount(&spi_fs, "0:", 1);
    if(retSD != FR_OK)
    {
        if(retSD == FR_NO_FILESYSTEM)
        {
            printf("f_mount 没有文件系统,开始格式化spi-flash\r\n");
            retSD = f_mkfs("0:", 0, 0);
            if(retSD != FR_OK)
            {
                printf("f_mkfs 格式化失败,err = %d\r\n", retSD);
 
                while(1);
            }
            else
            {
                printf("格式化成功,开始重新挂载spi-flash\r\n");
                retSD = f_mount(&spi_fs, "0:", 1);
                if(retSD != FR_OK)
                {
                    printf("f_mount 发生错误,err = %d\r\n", retSD);
                }
                else printf("spi-flash文件系统挂载成功\r\n");
            }
        }
        else
        {
            printf("f_mount 发生其他错误,err = %d\r\n", retSD);
            while(1);
        }
    }
    else printf("spi-flash文件系统挂载成功\r\n");
    /*----------------------- 文件系统测试:写测试 -----------------------------*/
    printf("\r\n****** 即将进行文件写入测试... ******\r\n");
    retSD = f_open(&fil, "0:sudaroot.txt", FA_OPEN_ALWAYS | FA_WRITE);
    if(retSD == FR_OK)
    {
        printf("打开/创建sudaroot.txt文件成功,向文件写入数据。\r\n");
        retSD = f_write(&fil, write_buf, strlen((const char *)write_buf), &count);
        if(retSD != FR_OK)
        {
            printf("f_write 发生错误,err = %d\r\n", retSD);
            printf("关闭打开的sudaroot.txt文件\r\n");
            count = 0;
            f_close(&fil);
        }
        else
        {
            printf("文件写入成功,写入字节数据:%d\n", count);
            printf("向文件写入的数据为:\r\n%s\r\n", write_buf);
            printf("关闭打开的sudaroot.txt文件\r\n");
            count = 0;
            f_close(&fil);
        }
    }
    else printf("打开/创建sudaroot.txt文件失败,err = %d\r\n", retSD);
    /*------------------- 文件系统测试:读测试 ------------------------------------*/
    printf("****** 即将进行文件读取测试... ******\r\n");
    retSD = f_open(&fil, "0:sudaroot.txt", FA_OPEN_EXISTING | FA_READ);
    if(retSD == FR_OK)
    {
        printf("打开sudaroot.txt文件成功\r\n");
        retSD = f_read(&fil, read_buf, sizeof(read_buf), &count);
        if(retSD != FR_OK)
        {
            printf("f_read 发生错误,err = %d\r\n", retSD);
            printf("关闭打开的sudaroot.txt文件\r\n");
            f_close(&fil);
        }
        else
        {
            printf("文件读取成功,读取字节数据:%d\n", count);
            printf("向文件读取的数据为:\r\n%s\r\n", read_buf);
            printf("关闭打开的sudaroot.txt文件\r\n");
            f_close(&fil);
        }
    }
    else printf("打开sudaroot.txt文件失败,err = %d\r\n", retSD);
}
  • 在main函数里面添加test()。
3、结果

在这里插入图片描述需要注意的是,由于配置阶段没有打开USE_LFN,所以如果文件名长度超过8位,或者后缀名超过3位则无法进行读写操作。

参考
  • 3
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值