所需工具
- 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、移植修改文件
- 添加W25Q128驱动文件,详情在这
- 修改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>© 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_SIZE | FLASH_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_SIZE | FLASH_SECTOR_SIZE | 只有当FF_MAX_SS > FF_MIN_SS时才需要这个命令。通俗点来说,告诉FATFS系统每个扇区有多少大容量 |
GET_SECTOR_COUNT | FLASH_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 SIZEx∗1024∗1024 |
- 在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位则无法进行读写操作。